import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { setup } from "../../__testData__/controller-test"; import { Test as SuperTest } from "supertest"; import * as ApeKeyDal from "../../../src/dal/ape-keys"; import { ObjectId } from "mongodb"; import * as Configuration from "../../../src/init/configuration"; import * as UserDal from "../../../src/dal/user"; const { mockApp, uid } = setup(); const configuration = Configuration.getCachedConfiguration(); describe("ApeKeyController", () => { const getUserMock = vi.spyOn(UserDal, "getPartialUser"); beforeEach(async () => { await enableApeKeysEndpoints(true); getUserMock.mockResolvedValue(user(uid, {})); vi.useFakeTimers(); vi.setSystemTime(1000); }); afterEach(() => { getUserMock.mockClear(); vi.useRealTimers(); }); describe("get ape keys", () => { const getApeKeysMock = vi.spyOn(ApeKeyDal, "getApeKeys"); afterEach(() => { getApeKeysMock.mockClear(); }); it("should get the users config", async () => { //GIVEN const keyOne = apeKeyDb(uid); const keyTwo = apeKeyDb(uid); getApeKeysMock.mockResolvedValue([keyOne, keyTwo]); //WHEN const { body } = await mockApp .get("/ape-keys") .set("Authorization", `Bearer ${uid}`) .expect(200); //THEN expect(body).toHaveProperty("message", "ApeKeys retrieved"); expect(body.data).toHaveProperty(keyOne._id.toHexString(), { name: keyOne.name, enabled: keyOne.enabled, createdOn: keyOne.createdOn, modifiedOn: keyOne.modifiedOn, lastUsedOn: keyOne.lastUsedOn, }); expect(body.data).toHaveProperty(keyTwo._id.toHexString(), { name: keyTwo.name, enabled: keyTwo.enabled, createdOn: keyTwo.createdOn, modifiedOn: keyTwo.modifiedOn, lastUsedOn: keyTwo.lastUsedOn, }); expect(body.data).keys([keyOne._id, keyTwo._id]); expect(getApeKeysMock).toHaveBeenCalledWith(uid); }); it("should fail if apeKeys endpoints are disabled", async () => { await expectFailForDisabledEndpoint( mockApp.get("/ape-keys").set("Authorization", `Bearer ${uid}`), ); }); it("should fail if user has no apeKey permissions", async () => { await expectFailForNoPermissions( mockApp.get("/ape-keys").set("Authorization", `Bearer ${uid}`), ); }); }); describe("add ape key", () => { const addApeKeyMock = vi.spyOn(ApeKeyDal, "addApeKey"); const countApeKeysMock = vi.spyOn(ApeKeyDal, "countApeKeysForUser"); beforeEach(() => { countApeKeysMock.mockResolvedValue(0); }); afterEach(() => { addApeKeyMock.mockClear(); countApeKeysMock.mockClear(); }); it("should add ape key", async () => { //GIVEN addApeKeyMock.mockResolvedValue("1"); //WHEN const { body } = await mockApp .post("/ape-keys") .set("Authorization", `Bearer ${uid}`) .send({ name: "test", enabled: true }) .expect(200); expect(body.message).toEqual("ApeKey generated"); expect(body.data).keys("apeKey", "apeKeyDetails", "apeKeyId"); expect(body.data.apeKey).not.toBeNull(); expect(body.data.apeKeyDetails).toStrictEqual({ createdOn: 1000, enabled: true, lastUsedOn: -1, modifiedOn: 1000, name: "test", }); expect(body.data.apeKeyId).toEqual("1"); expect(addApeKeyMock).toHaveBeenCalledWith( expect.objectContaining({ createdOn: 1000, enabled: true, lastUsedOn: -1, modifiedOn: 1000, name: "test", uid: uid, useCount: 0, }), ); }); it("should fail without mandatory properties", async () => { //WHEN const { body } = await mockApp .post("/ape-keys") .send({}) .set("Authorization", `Bearer ${uid}`) .expect(422); //THEN expect(body).toStrictEqual({ message: "Invalid request data schema", validationErrors: [`"name" Required`, `"enabled" Required`], }); }); it("should fail with extra properties", async () => { //WHEN const { body } = await mockApp .post("/ape-keys") .send({ name: "test", enabled: true, extra: "value" }) .set("Authorization", `Bearer ${uid}`) .expect(422); //THEN expect(body).toStrictEqual({ message: "Invalid request data schema", validationErrors: ["Unrecognized key(s) in object: 'extra'"], }); }); it("should fail if max apeKeys is reached", async () => { //GIVEN countApeKeysMock.mockResolvedValue(1); //WHEN const { body } = await mockApp .post("/ape-keys") .send({ name: "test", enabled: false }) .set("Authorization", `Bearer ${uid}`) .expect(409); //THEN expect(body.message).toEqual( "Maximum number of ApeKeys have been generated", ); }); it("should fail if apeKeys endpoints are disabled", async () => { await expectFailForDisabledEndpoint( mockApp .post("/ape-keys") .send({ name: "test", enabled: false }) .set("Authorization", `Bearer ${uid}`), ); }); it("should fail if user has no apeKey permissions", async () => { await expectFailForNoPermissions( mockApp .post("/ape-keys") .send({ name: "test", enabled: false }) .set("Authorization", `Bearer ${uid}`), ); }); }); describe("edit ape key", () => { const editApeKeyMock = vi.spyOn(ApeKeyDal, "editApeKey"); const apeKeyId = new ObjectId().toHexString(); afterEach(() => { editApeKeyMock.mockClear(); }); it("should edit ape key", async () => { //GIVEN editApeKeyMock.mockResolvedValue(); //WHEN const { body } = await mockApp .patch(`/ape-keys/${apeKeyId}`) .send({ name: "new", enabled: false }) .set("Authorization", `Bearer ${uid}`) .expect(200); //THEN expect(body.message).toEqual("ApeKey updated"); expect(editApeKeyMock).toHaveBeenCalledWith(uid, apeKeyId, "new", false); }); it("should edit ape key with single property", async () => { //GIVEN editApeKeyMock.mockResolvedValue(); //WHEN const { body } = await mockApp .patch(`/ape-keys/${apeKeyId}`) .send({ name: "new" }) .set("Authorization", `Bearer ${uid}`) .expect(200); //THEN expect(body.message).toEqual("ApeKey updated"); expect(editApeKeyMock).toHaveBeenCalledWith( uid, apeKeyId, "new", undefined, ); }); it("should fail with missing path", async () => { //GIVEN //WHEN await mockApp .patch(`/ape-keys/`) .set("Authorization", `Bearer ${uid}`) .expect(404); }); it("should fail with extra properties", async () => { //GIVEN //WHEN const { body } = await mockApp .patch(`/ape-keys/${apeKeyId}`) .send({ name: "new", extra: "value" }) .set("Authorization", `Bearer ${uid}`) .expect(422); //THEN expect(body).toStrictEqual({ message: "Invalid request data schema", validationErrors: ["Unrecognized key(s) in object: 'extra'"], }); }); it("should fail if apeKeys endpoints are disabled", async () => { await expectFailForDisabledEndpoint( mockApp .patch(`/ape-keys/${apeKeyId}`) .send({ name: "test", enabled: false }) .set("Authorization", `Bearer ${uid}`), ); }); it("should fail if user has no apeKey permissions", async () => { await expectFailForNoPermissions( mockApp .patch(`/ape-keys/${apeKeyId}`) .send({ name: "test", enabled: false }) .set("Authorization", `Bearer ${uid}`), ); }); }); describe("delete ape key", () => { const deleteApeKeyMock = vi.spyOn(ApeKeyDal, "deleteApeKey"); const apeKeyId = new ObjectId().toHexString(); afterEach(() => { deleteApeKeyMock.mockClear(); }); it("should delete ape key", async () => { //GIVEN deleteApeKeyMock.mockResolvedValue(); //WHEN const { body } = await mockApp .delete(`/ape-keys/${apeKeyId}`) .set("Authorization", `Bearer ${uid}`) .expect(200); //THEN expect(body.message).toEqual("ApeKey deleted"); expect(deleteApeKeyMock).toHaveBeenCalledWith(uid, apeKeyId); }); it("should fail with missing path", async () => { //GIVEN //WHEN await mockApp .delete(`/ape-keys/`) .set("Authorization", `Bearer ${uid}`) .expect(404); }); it("should fail if apeKeys endpoints are disabled", async () => { await expectFailForDisabledEndpoint( mockApp .delete(`/ape-keys/${apeKeyId}`) .set("Authorization", `Bearer ${uid}`), ); }); it("should fail if user has no apeKey permissions", async () => { await expectFailForNoPermissions( mockApp .delete(`/ape-keys/${apeKeyId}`) .set("Authorization", `Bearer ${uid}`), ); }); }); async function expectFailForNoPermissions(call: SuperTest): Promise { getUserMock.mockResolvedValue(user(uid, { canManageApeKeys: false })); const { body } = await call.expect(403); expect(body.message).toEqual( "You have lost access to ape keys, please contact support", ); } async function expectFailForDisabledEndpoint(call: SuperTest): Promise { await enableApeKeysEndpoints(false); const { body } = await call.expect(503); expect(body.message).toEqual("ApeKeys are currently disabled."); } }); function apeKeyDb( uid: string, data?: Partial, ): ApeKeyDal.DBApeKey { return { _id: new ObjectId(), uid, hash: "hash", useCount: 1, name: "name", enabled: true, createdOn: Math.random() * Date.now(), lastUsedOn: Math.random() * Date.now(), modifiedOn: Math.random() * Date.now(), ...data, }; } async function enableApeKeysEndpoints(enabled: boolean): Promise { const mockConfig = await configuration; mockConfig.apeKeys = { ...mockConfig.apeKeys, endpointsEnabled: enabled, maxKeysPerUser: 1, }; vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue( mockConfig, ); } function user(uid: string, data: Partial): UserDal.DBUser { return { uid, ...data, } as UserDal.DBUser; }