-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: write tests for collections service
- Loading branch information
Showing
1 changed file
with
163 additions
and
148 deletions.
There are no files selected for viewing
311 changes: 163 additions & 148 deletions
311
api.planx.uk/modules/analytics/metabase/collection/collection.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,187 +1,202 @@ | ||
import supertest from "supertest"; | ||
import express from "express"; | ||
import { metabaseClient } from "../shared/client.js"; | ||
import { checkCollections, newCollection } from "./service.js"; | ||
import { | ||
checkCollectionsController, | ||
newCollectionController, | ||
} from "./controller.js"; | ||
import { validate } from "../../../../shared/middleware/validate.js"; | ||
import { newCollectionSchema } from "./types.js"; | ||
|
||
// Mock metabaseClient & error | ||
vi.mock("../shared/client", () => ({ | ||
MetabaseError: class MetabaseError extends Error { | ||
constructor( | ||
message: string, | ||
public statusCode?: number, | ||
public response?: unknown, | ||
) { | ||
super(message); | ||
this.name = "MetabaseError"; | ||
} | ||
}, | ||
metabaseClient: { | ||
get: vi.fn(), | ||
post: vi.fn(), | ||
}, | ||
})); | ||
|
||
describe("Metabase collection module", () => { | ||
const app = express(); | ||
app.use(express.json()); | ||
|
||
// Set up test routes | ||
app.get("/collections/check", checkCollectionsController); | ||
app.post( | ||
"/collections/new", | ||
validate(newCollectionSchema), | ||
newCollectionController, | ||
); | ||
import { newCollection, getCollection } from "./service.js"; | ||
import nock from "nock"; | ||
import { MetabaseError } from "../shared/client.js"; | ||
|
||
describe("newCollection", () => { | ||
beforeEach(() => { | ||
vi.clearAllMocks(); | ||
nock.cleanAll(); | ||
}); | ||
|
||
afterEach(() => { | ||
vi.resetAllMocks(); | ||
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); | ||
}); | ||
|
||
describe("Services", () => { | ||
describe("checkCollections", () => { | ||
it("should return collection ID when collection exists", async () => { | ||
const mockCollections = [ | ||
{ id: 1, name: "Test Council" }, | ||
{ id: 2, name: "Another Council" }, | ||
]; | ||
|
||
vi.mocked(metabaseClient.get).mockResolvedValueOnce({ | ||
data: mockCollections, | ||
}); | ||
|
||
const result = await checkCollections("test council"); | ||
expect(result).toBe(1); | ||
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, | ||
}); | ||
|
||
it("should return false when collection doesn't exist", async () => { | ||
vi.mocked(metabaseClient.get).mockResolvedValueOnce({ | ||
data: [], | ||
}); | ||
// 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 result = await checkCollections("nonexistent"); | ||
expect(result).toBe(false); | ||
}); | ||
const collectionId = await newCollection({ | ||
name: testName, | ||
parentId: 100, | ||
}); | ||
|
||
it("should handle API errors", async () => { | ||
vi.mocked(metabaseClient.get).mockRejectedValueOnce( | ||
new Error("API Error"), | ||
); | ||
// Check the ID is returned correctly | ||
expect(collectionId).toBe(123); | ||
|
||
await expect(checkCollections("test")).rejects.toThrow("API Error"); | ||
}); | ||
// 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); | ||
}); | ||
|
||
describe("newCollection", () => { | ||
const mockCollection = { | ||
name: "Test Collection", | ||
description: "Test Description", | ||
parent_id: 1, | ||
}; | ||
test("successfully creates a new collection and returns its ID", async () => { | ||
const testName = "Example council"; | ||
|
||
it("should create a new collection successfully", async () => { | ||
vi.mocked(metabaseClient.post).mockResolvedValueOnce({ | ||
data: { id: 1, name: mockCollection.name }, | ||
}); | ||
// Mock collection check endpoint | ||
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []); | ||
|
||
const result = await newCollection(mockCollection); | ||
expect(result).toBe(1); | ||
// Mock collection creation endpoint | ||
nock(process.env.METABASE_URL_EXT!) | ||
.post("/api/collection/", { | ||
name: testName, | ||
}) | ||
.reply(200, { | ||
id: 123, | ||
name: testName, | ||
}); | ||
|
||
it("should handle API errors", async () => { | ||
vi.mocked(metabaseClient.post).mockRejectedValueOnce( | ||
new Error("Creation failed"), | ||
); | ||
|
||
await expect(newCollection(mockCollection)).rejects.toThrow( | ||
"Creation failed", | ||
); | ||
}); | ||
const collection = await newCollection({ | ||
name: testName, | ||
}); | ||
|
||
expect(collection).toBe(123); | ||
}); | ||
|
||
describe("Controllers", () => { | ||
describe("GET /collections/check", () => { | ||
it("should return 400 if name parameter is missing", async () => { | ||
const response = await supertest(app).get("/collections/check"); | ||
expect(response.status).toBe(400); | ||
expect(response.body.error).toBe("Name parameter is required"); | ||
}); | ||
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"); | ||
}); | ||
|
||
it("should return 200 and collection ID if found", async () => { | ||
vi.mocked(metabaseClient.get).mockResolvedValueOnce({ | ||
data: [{ id: 1, name: "Test Council" }], | ||
}); | ||
test("throws error if API error", async () => { | ||
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(400, { | ||
message: "Bad request", | ||
}); | ||
|
||
const response = await supertest(app) | ||
.get("/collections/check") | ||
.query({ name: "test council" }); | ||
await expect( | ||
newCollection({ | ||
name: "Test Collection", | ||
}), | ||
).rejects.toThrow(MetabaseError); | ||
}); | ||
|
||
expect(response.status).toBe(200); | ||
expect(response.body).toBe(1); | ||
}); | ||
test("correctly transforms request to snake case", async () => { | ||
const testData = { | ||
name: "Test Collection", | ||
parentId: 123, // This should become parent_id | ||
}; | ||
|
||
it("should handle server errors", async () => { | ||
vi.mocked(metabaseClient.get).mockRejectedValueOnce( | ||
new Error("Server error"), | ||
); | ||
// Mock the check for existing collections | ||
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []); | ||
|
||
const response = await supertest(app) | ||
.get("/collections/check") | ||
.query({ name: "test" }); | ||
// Create a variable to store the request body | ||
let capturedBody: any; | ||
|
||
expect(response.status).toBe(500); | ||
expect(response.body.error).toBe("Failed to fetch collections"); | ||
}); | ||
}); | ||
// 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 }); | ||
|
||
describe("POST /collections/new", () => { | ||
const validRequest = { | ||
name: "Test Collection", | ||
description: "Test Description", | ||
parentId: 1, | ||
}; | ||
await newCollection(testData); | ||
|
||
it("should create a new collection with valid data", async () => { | ||
vi.mocked(metabaseClient.post).mockResolvedValueOnce({ | ||
data: { id: 1, name: validRequest.name }, | ||
}); | ||
// Verify the transformation | ||
expect(capturedBody).toHaveProperty("parent_id", 123); | ||
expect(capturedBody).not.toHaveProperty("parentId"); | ||
}); | ||
}); | ||
|
||
const response = await supertest(app) | ||
.post("/collections/new") | ||
.send(validRequest); | ||
describe("edge cases", () => { | ||
test("handles missing name", async () => { | ||
await expect( | ||
newCollection({ | ||
name: "", | ||
}), | ||
).rejects.toThrow(); | ||
}); | ||
|
||
expect(response.status).toBe(201); | ||
expect(response.body.data).toBeDefined(); | ||
}); | ||
test("handles names with special characters", async () => { | ||
const specialName = "@#$%^&*"; | ||
|
||
it("should handle validation errors", async () => { | ||
const response = await supertest(app).post("/collections/new").send({}); | ||
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []); | ||
|
||
expect(response.status).toBe(400); | ||
nock(process.env.METABASE_URL_EXT!) | ||
.post("/api/collection/", { | ||
name: specialName, | ||
}) | ||
.reply(200, { | ||
id: 789, | ||
name: specialName, | ||
}); | ||
|
||
it("should handle service errors", async () => { | ||
vi.mocked(metabaseClient.post).mockRejectedValueOnce( | ||
new Error("Service error"), | ||
); | ||
const collection = await newCollection({ | ||
name: specialName, | ||
}); | ||
expect(collection).toBe(789); | ||
}); | ||
|
||
test("handles very long names", async () => { | ||
const longName = "A".repeat(101); | ||
|
||
const response = await supertest(app) | ||
.post("/collections/new") | ||
.send(validRequest); | ||
nock(process.env.METABASE_URL_EXT!).get("/api/collection/").reply(200, []); | ||
|
||
expect(response.status).toBe(400); | ||
expect(response.body.error).toBe("Service error"); | ||
nock(process.env.METABASE_URL_EXT!) | ||
.post("/api/collection/", { | ||
name: longName, | ||
}) | ||
.reply(400, { | ||
message: "Name too long", | ||
}); | ||
}); | ||
|
||
await expect( | ||
newCollection({ | ||
name: longName, | ||
}), | ||
).rejects.toThrow(); | ||
}); | ||
}); |