From 505099ee2665c781bdc726fe2eadfddd97f28587 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 3 Oct 2022 20:09:53 -0800 Subject: [PATCH] api refactor / registration tests --- frontend/composables/use-api.ts | 6 +- frontend/lib/api/__test__/public.test.ts | 64 ++++++++++++++++--- frontend/lib/api/__test__/test-utils.ts | 6 +- frontend/lib/api/__test__/user/items.test.ts | 4 +- frontend/lib/api/__test__/user/labels.test.ts | 4 +- .../lib/api/__test__/user/locations.test.ts | 4 +- frontend/lib/api/classes/group.ts | 11 ++++ frontend/lib/api/classes/users.ts | 17 +++++ frontend/lib/api/user.ts | 25 +++++--- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 7 ++ frontend/stores/auth.ts | 4 +- 12 files changed, 120 insertions(+), 33 deletions(-) create mode 100644 frontend/lib/api/classes/group.ts create mode 100644 frontend/lib/api/classes/users.ts diff --git a/frontend/composables/use-api.ts b/frontend/composables/use-api.ts index 4eb753f..69da2ed 100644 --- a/frontend/composables/use-api.ts +++ b/frontend/composables/use-api.ts @@ -1,5 +1,5 @@ import { PublicApi } from "~~/lib/api/public"; -import { UserApi } from "~~/lib/api/user"; +import { UserClient } from "~~/lib/api/user"; import { Requests } from "~~/lib/requests"; import { useAuthStore } from "~~/stores/auth"; @@ -28,7 +28,7 @@ export function usePublicApi(): PublicApi { return new PublicApi(requests); } -export function useUserApi(): UserApi { +export function useUserApi(): UserClient { const authStore = useAuthStore(); const requests = new Requests("", () => authStore.token, {}); @@ -43,5 +43,5 @@ export function useUserApi(): UserApi { requests.addResponseInterceptor(observer.handler); } - return new UserApi(requests); + return new UserClient(requests); } diff --git a/frontend/lib/api/__test__/public.test.ts b/frontend/lib/api/__test__/public.test.ts index 6dd618e..cb3a9c3 100644 --- a/frontend/lib/api/__test__/public.test.ts +++ b/frontend/lib/api/__test__/public.test.ts @@ -1,5 +1,17 @@ import { describe, test, expect } from "vitest"; -import { client, userClient } from "./test-utils"; +import { faker } from "@faker-js/faker"; +import { UserRegistration } from "../types/data-contracts"; +import { client, sharedUserClient, userClient } from "./test-utils"; + +function userFactory(): UserRegistration { + return { + email: faker.internet.email(), + password: faker.internet.password(), + name: faker.name.firstName(), + groupName: faker.animal.cat(), + token: "", + }; +} describe("[GET] /api/v1/status", () => { test("server should respond", async () => { @@ -10,14 +22,9 @@ describe("[GET] /api/v1/status", () => { }); }); -describe("first time user workflow (register, login)", () => { +describe("first time user workflow (register, login, join group)", () => { const api = client(); - const userData = { - groupName: "test-group", - email: "test-user@email.com", - name: "test-user", - password: "test-password", - }; + const userData = userFactory(); test("user should be able to register", async () => { const { response } = await api.register(userData); @@ -32,8 +39,47 @@ describe("first time user workflow (register, login)", () => { // Cleanup const userApi = userClient(data.token); { - const { response } = await userApi.deleteAccount(); + const { response } = await userApi.user.delete(); expect(response.status).toBe(204); } }); + + test("user should be able to join create join token and have user signup", async () => { + // Setup User 1 Token + + const client = await sharedUserClient(); + const { data: user1 } = await client.user.self(); + + const { response, data } = await client.group.createInvitation({ + expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), + uses: 1, + }); + + expect(response.status).toBe(201); + expect(data.token).toBeTruthy(); + + // Create User 2 with token + + const duplicateUser = userFactory(); + duplicateUser.token = data.token; + + const { response: registerResp } = await api.register(duplicateUser); + expect(registerResp.status).toBe(204); + + const { response: loginResp, data: loginData } = await api.login(duplicateUser.email, duplicateUser.password); + expect(loginResp.status).toBe(200); + + // Get Self and Assert + + const client2 = userClient(loginData.token); + + const { data: user2 } = await client2.user.self(); + + user2.item.groupName = user1.item.groupName; + + // Cleanup User 2 + + const { response: deleteResp } = await client2.user.delete(); + expect(deleteResp.status).toBe(204); + }); }); diff --git a/frontend/lib/api/__test__/test-utils.ts b/frontend/lib/api/__test__/test-utils.ts index 0be76d2..cab9b9a 100644 --- a/frontend/lib/api/__test__/test-utils.ts +++ b/frontend/lib/api/__test__/test-utils.ts @@ -3,7 +3,7 @@ import { Requests } from "../../requests"; import { overrideParts } from "../base/urls"; import { PublicApi } from "../public"; import * as config from "../../../test/config"; -import { UserApi } from "../user"; +import { UserClient } from "../user"; export function client() { overrideParts(config.BASE_URL, "/api/v1"); @@ -14,7 +14,7 @@ export function client() { export function userClient(token: string) { overrideParts(config.BASE_URL, "/api/v1"); const requests = new Requests("", token); - return new UserApi(requests); + return new UserClient(requests); } const cache = { @@ -25,7 +25,7 @@ const cache = { * Shared UserApi token for tests where the creation of a user is _not_ import * to the test. This is useful for tests that are testing the user API itself. */ -export async function sharedUserClient(): Promise { +export async function sharedUserClient(): Promise { if (cache.token) { return userClient(cache.token); } diff --git a/frontend/lib/api/__test__/user/items.test.ts b/frontend/lib/api/__test__/user/items.test.ts index 75ca5cf..9bdea4d 100644 --- a/frontend/lib/api/__test__/user/items.test.ts +++ b/frontend/lib/api/__test__/user/items.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "vitest"; import { LocationOut } from "../../types/data-contracts"; import { AttachmentTypes } from "../../types/non-generated"; -import { UserApi } from "../../user"; +import { UserClient } from "../../user"; import { sharedUserClient } from "../test-utils"; describe("user should be able to create an item and add an attachment", () => { @@ -10,7 +10,7 @@ describe("user should be able to create an item and add an attachment", () => { * useLocatio sets up a location resource for testing, and returns a function * that can be used to delete the location from the backend server. */ - async function useLocation(api: UserApi): Promise<[LocationOut, () => Promise]> { + async function useLocation(api: UserClient): Promise<[LocationOut, () => Promise]> { const { response, data } = await api.locations.create({ name: `__test__.location.name_${increment}`, description: `__test__.location.description_${increment}`, diff --git a/frontend/lib/api/__test__/user/labels.test.ts b/frontend/lib/api/__test__/user/labels.test.ts index b2b7e0d..1fc414a 100644 --- a/frontend/lib/api/__test__/user/labels.test.ts +++ b/frontend/lib/api/__test__/user/labels.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; import { LabelOut } from "../../types/data-contracts"; -import { UserApi } from "../../user"; +import { UserClient } from "../../user"; import { sharedUserClient } from "../test-utils"; describe("locations lifecycle (create, update, delete)", () => { @@ -10,7 +10,7 @@ describe("locations lifecycle (create, update, delete)", () => { * useLabel sets up a label resource for testing, and returns a function * that can be used to delete the label from the backend server. */ - async function useLabel(api: UserApi): Promise<[LabelOut, () => Promise]> { + async function useLabel(api: UserClient): Promise<[LabelOut, () => Promise]> { const { response, data } = await api.labels.create({ name: `__test__.label.name_${increment}`, description: `__test__.label.description_${increment}`, diff --git a/frontend/lib/api/__test__/user/locations.test.ts b/frontend/lib/api/__test__/user/locations.test.ts index 0e6e9af..8583074 100644 --- a/frontend/lib/api/__test__/user/locations.test.ts +++ b/frontend/lib/api/__test__/user/locations.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "vitest"; import { LocationOut } from "../../types/data-contracts"; -import { UserApi } from "../../user"; +import { UserClient } from "../../user"; import { sharedUserClient } from "../test-utils"; describe("locations lifecycle (create, update, delete)", () => { @@ -10,7 +10,7 @@ describe("locations lifecycle (create, update, delete)", () => { * useLocatio sets up a location resource for testing, and returns a function * that can be used to delete the location from the backend server. */ - async function useLocation(api: UserApi): Promise<[LocationOut, () => Promise]> { + async function useLocation(api: UserClient): Promise<[LocationOut, () => Promise]> { const { response, data } = await api.locations.create({ name: `__test__.location.name_${increment}`, description: `__test__.location.description_${increment}`, diff --git a/frontend/lib/api/classes/group.ts b/frontend/lib/api/classes/group.ts new file mode 100644 index 0000000..5a687f1 --- /dev/null +++ b/frontend/lib/api/classes/group.ts @@ -0,0 +1,11 @@ +import { BaseAPI, route } from "../base"; +import { GroupInvitation, GroupInvitationCreate } from "../types/data-contracts"; + +export class GroupApi extends BaseAPI { + createInvitation(data: GroupInvitationCreate) { + return this.http.post({ + url: route("/groups/invitations"), + body: data, + }); + } +} diff --git a/frontend/lib/api/classes/users.ts b/frontend/lib/api/classes/users.ts new file mode 100644 index 0000000..39f03b4 --- /dev/null +++ b/frontend/lib/api/classes/users.ts @@ -0,0 +1,17 @@ +import { BaseAPI, route } from "../base"; +import { UserOut } from "../types/data-contracts"; +import { Result } from "../types/non-generated"; + +export class UserApi extends BaseAPI { + public self() { + return this.http.get>({ url: route("/users/self") }); + } + + public logout() { + return this.http.post({ url: route("/users/logout") }); + } + + public delete() { + return this.http.delete({ url: route("/users/self") }); + } +} diff --git a/frontend/lib/api/user.ts b/frontend/lib/api/user.ts index 7a6b122..d714bef 100644 --- a/frontend/lib/api/user.ts +++ b/frontend/lib/api/user.ts @@ -1,37 +1,42 @@ -import { BaseAPI, route } from "./base"; +import { BaseAPI } from "./base"; import { ItemsApi } from "./classes/items"; import { LabelsApi } from "./classes/labels"; import { LocationsApi } from "./classes/locations"; -import { UserOut } from "./types/data-contracts"; +import { GroupApi } from "./classes/group"; +import { UserApi } from "./classes/users"; import { Requests } from "~~/lib/requests"; -export type Result = { - item: T; -}; - -export class UserApi extends BaseAPI { +export class UserClient extends BaseAPI { locations: LocationsApi; labels: LabelsApi; items: ItemsApi; + group: GroupApi; + user: UserApi; + constructor(requests: Requests) { super(requests); this.locations = new LocationsApi(requests); this.labels = new LabelsApi(requests); this.items = new ItemsApi(requests); + this.group = new GroupApi(requests); + this.user = new UserApi(requests); Object.freeze(this); } + /** @deprecated use this.user.self() */ public self() { - return this.http.get>({ url: route("/users/self") }); + return this.user.self(); } + /** @deprecated use this.user.logout() */ public logout() { - return this.http.post({ url: route("/users/logout") }); + return this.user.logout(); } + /** @deprecated use this.user.delete() */ public deleteAccount() { - return this.http.delete({ url: route("/users/self") }); + return this.user.delete(); } } diff --git a/frontend/package.json b/frontend/package.json index 595b8d3..5c6eb39 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "test:watch": " TEST_SHUTDOWN_API_SERVER=false vitest --config ./test/vitest.config.ts" }, "devDependencies": { + "@faker-js/faker": "^7.5.0", "@nuxtjs/eslint-config-typescript": "^11.0.0", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 067292c..3dbfc99 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -1,6 +1,7 @@ lockfileVersion: 5.4 specifiers: + '@faker-js/faker': ^7.5.0 '@iconify/vue': ^3.2.1 '@nuxtjs/eslint-config-typescript': ^11.0.0 '@nuxtjs/tailwindcss': ^5.3.2 @@ -44,6 +45,7 @@ dependencies: vue: 3.2.39 devDependencies: + '@faker-js/faker': 7.5.0 '@nuxtjs/eslint-config-typescript': 11.0.0_7ilbxdl5iguzcjriqqcg2m5cku '@typescript-eslint/eslint-plugin': 5.38.0_4gkcvl6qsi23tqqawfqgcwtp54 '@typescript-eslint/parser': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku @@ -381,6 +383,11 @@ packages: transitivePeerDependencies: - supports-color + /@faker-js/faker/7.5.0: + resolution: {integrity: sha512-8wNUCCUHvfvI0gQpDUho/3gPzABffnCn5um65F8dzQ86zz6dlt4+nmAA7PQUc8L+eH+9RgR/qzy5N/8kN0Ozdw==} + engines: {node: '>=14.0.0', npm: '>=6.0.0'} + dev: true + /@humanwhocodes/config-array/0.10.5: resolution: {integrity: sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==} engines: {node: '>=10.10.0'} diff --git a/frontend/stores/auth.ts b/frontend/stores/auth.ts index ef46a70..2a2d2b7 100644 --- a/frontend/stores/auth.ts +++ b/frontend/stores/auth.ts @@ -1,6 +1,6 @@ import { defineStore } from "pinia"; import { useLocalStorage } from "@vueuse/core"; -import { UserApi } from "~~/lib/api/user"; +import { UserClient } from "~~/lib/api/user"; import { UserOut } from "~~/lib/api/types/data-contracts"; export const useAuthStore = defineStore("auth", { @@ -23,7 +23,7 @@ export const useAuthStore = defineStore("auth", { }, }, actions: { - async logout(api: UserApi) { + async logout(api: UserClient) { const result = await api.logout(); if (result.error) {