Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: test for Metabase collection controller and services #3974

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 202 additions & 1 deletion api.planx.uk/modules/analytics/metabase/collection/collection.test.ts
Original file line number Diff line number Diff line change
@@ -1 +1,202 @@
test.todo("should test collection check and creation");
import { newCollection, getCollection } from "./service.js";
import nock from "nock";
import { MetabaseError } from "../shared/client.js";

describe("newCollection", () => {
beforeEach(() => {
nock.cleanAll();
});

test("returns a collection ID if collection exists", async () => {
// Mock collection check endpoint
nock(process.env.METABASE_URL_EXT!)
.get("/api/collection/")
.reply(200, [
{ id: 20, name: "Barnet" },
{ id: 21, name: "Another collection" },
]);

const collection = await newCollection({
name: "Barnet",
});
console.log("HERE IS THE COLLECTION: ", collection);
expect(collection).toBe(20);
});

test("successfully places new collection in parent", async () => {
const testName = "Example council";

// Mock collection check endpoint
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []);

// Mock collection creation endpoint
nock(process.env.METABASE_URL_EXT!)
.post("/api/collection/", {
name: testName,
parent_id: 100,
})
.reply(200, {
id: 123,
name: testName,
parent_id: 100,
});

// Mock GET request for verifying the new collection
nock(process.env.METABASE_URL_EXT!).get("/api/collection/123").reply(200, {
id: 123,
name: testName,
parent_id: 100,
});

const collectionId = await newCollection({
name: testName,
parentId: 100,
});

// Check the ID is returned correctly
expect(collectionId).toBe(123);

// Verify the collection details using the service function
const collection = await getCollection(collectionId);
expect(collection.parent_id).toBe(100);
});

test("returns collection correctly no matter collection name case", async () => {
// Mock collection check endpoint
nock(process.env.METABASE_URL_EXT!)
.get("/api/collection/")
.reply(200, [
{ id: 20, name: "Barnet" },
{ id: 21, name: "Another collection" },
]);

const collection = await newCollection({
name: "BARNET",
});
expect(collection).toBe(20);
});

test("successfully creates a new collection and returns its ID", async () => {
const testName = "Example council";

// Mock collection check endpoint
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []);

// Mock collection creation endpoint
nock(process.env.METABASE_URL_EXT!)
.post("/api/collection/", {
name: testName,
})
.reply(200, {
id: 123,
name: testName,
});

const collection = await newCollection({
name: testName,
});

expect(collection).toBe(123);
});

test("throws error if network failure", async () => {
nock(process.env.METABASE_URL_EXT!)
.get("/api/collection/")
.replyWithError("Network error occurred");

await expect(
newCollection({
name: "Test Collection",
}),
).rejects.toThrow("Network error occurred");
});

test("throws error if API error", async () => {
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(400, {
message: "Bad request",
});

await expect(
newCollection({
name: "Test Collection",
}),
).rejects.toThrow(MetabaseError);
});

test("correctly transforms request to snake case", async () => {
const testData = {
name: "Test Collection",
parentId: 123, // This should become parent_id
};

// Mock the check for existing collections
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []);

// Create a variable to store the request body
let capturedBody: any;

Check warning on line 136 in api.planx.uk/modules/analytics/metabase/collection/collection.test.ts

View workflow job for this annotation

GitHub Actions / Run API Tests

Unexpected any. Specify a different type

// Mock and verify the POST request, capturing the body
nock(process.env.METABASE_URL_EXT!)
.post("/api/collection/", (body) => {
capturedBody = body;
return true; // Return true to indicate the request matches
})
.reply(200, { id: 456 });

await newCollection(testData);

// Verify the transformation
expect(capturedBody).toHaveProperty("parent_id", 123);
expect(capturedBody).not.toHaveProperty("parentId");
});
});

describe("edge cases", () => {
test("handles missing name", async () => {
await expect(
newCollection({
name: "",
}),
).rejects.toThrow();
});

test("handles names with special characters", async () => {
const specialName = "@#$%^&*";

nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []);

nock(process.env.METABASE_URL_EXT!)
.post("/api/collection/", {
name: specialName,
})
.reply(200, {
id: 789,
name: specialName,
});

const collection = await newCollection({
name: specialName,
});
expect(collection).toBe(789);
});

test("handles very long names", async () => {
const longName = "A".repeat(101);

nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []);

nock(process.env.METABASE_URL_EXT!)
.post("/api/collection/", {
name: longName,
})
.reply(400, {
message: "Name too long",
});

await expect(
newCollection({
name: longName,
}),
).rejects.toThrow();
});
});
27 changes: 20 additions & 7 deletions api.planx.uk/modules/analytics/metabase/collection/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@
}
}

/**
* First checks if a collection with a specified name exists.
* If it exists, return an object that includes its id. If not, create the collection.
* @params `name` is required, but `description` and `parentId` are optional.
* @returns `response.data`, so use dot notation to access `id` or `parentId`.
*/
export async function newCollection({
name,
description,
parent_id,
parentId,
}: NewCollectionParams): Promise<any> {

Check warning on line 27 in api.planx.uk/modules/analytics/metabase/collection/service.ts

View workflow job for this annotation

GitHub Actions / Run API Tests

Unexpected any. Specify a different type
try {
// Check if collection exists
const existingCollectionId = await checkCollections(name);
Expand All @@ -33,19 +39,16 @@
const requestBody = toSnakeCase({
name,
description,
parent_id,
parentId,
});

// Remove undefined properties
Object.keys(requestBody).forEach(
(key) => requestBody[key] === undefined && delete requestBody[key],
);

const response = await client.post(`/api/collection/`, {
name,
description,
parent_id,
});
const response = await client.post(`/api/collection/`, requestBody);

console.log(
`New collection: ${response.data.name}, new collection ID: ${response.data.id}`,
);
Expand All @@ -59,14 +62,14 @@
/**
* Checks if a collection exists with name matching `teamName` exists.
* Returns the matching collection ID if exists, otherwise false. */
export async function checkCollections(teamName: string): Promise<any> {

Check warning on line 65 in api.planx.uk/modules/analytics/metabase/collection/service.ts

View workflow job for this annotation

GitHub Actions / Run API Tests

Unexpected any. Specify a different type
try {
console.log("Checking for collection: ", teamName);

// Get collections from Metabase
const response = await client.get(`/api/collection/`);

const matchingCollection = response.data.find((collection: any) =>

Check warning on line 72 in api.planx.uk/modules/analytics/metabase/collection/service.ts

View workflow job for this annotation

GitHub Actions / Run API Tests

Unexpected any. Specify a different type
collection.name.toLowerCase().includes(teamName.toLowerCase()),
);

Expand All @@ -88,3 +91,13 @@
throw error;
}
}

/**
* Retrieves info on a collection from Metabase, use to check a parent
* @param id
* @returns
*/
export async function getCollection(id: number): Promise<any> {

Check warning on line 100 in api.planx.uk/modules/analytics/metabase/collection/service.ts

View workflow job for this annotation

GitHub Actions / Run API Tests

Unexpected any. Specify a different type
const response = await client.get(`/api/collection/${id}`);
return response.data;
}
4 changes: 2 additions & 2 deletions api.planx.uk/modules/analytics/metabase/collection/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface NewCollectionParams {
/** Optional; if the collection is a child of a parent, specify parent ID here
* For council teams, parent collection should be 58
*/
parent_id?: number;
parentId?: number;
}

/** Metbase collection ID for the the "Council" collection **/
Expand All @@ -22,7 +22,7 @@ export const newCollectionSchema = z.object({
body: z.object({
name: z.string(),
description: z.string().optional(),
parent_id: z.number().default(COUNCIL_COLLECTION_ID),
parentId: z.number().default(COUNCIL_COLLECTION_ID),
}),
});

Expand Down
Loading