Skip to content

Commit 15c5263

Browse files
#497 - feat: add api lib for destruction list co review
1 parent 24f61e7 commit 15c5263

File tree

6 files changed

+239
-0
lines changed

6 files changed

+239
-0
lines changed

frontend/src/fixtures/coReview.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { CoReview } from "../lib/api/coReview";
2+
import { destructionListFactory } from "./destructionList";
3+
import { createArrayFactory, createObjectFactory } from "./factory";
4+
import { beoordelaarFactory } from "./user";
5+
6+
const FIXTURE_CO_REVIEW: CoReview = {
7+
pk: 1,
8+
destructionList: destructionListFactory().uuid,
9+
author: beoordelaarFactory(),
10+
listFeedback: "",
11+
created: "2023-09-15T21:36:00.000000+01:00",
12+
};
13+
14+
const coReviewFactory = createObjectFactory(FIXTURE_CO_REVIEW);
15+
const coReviewsFactory = createArrayFactory([FIXTURE_CO_REVIEW]);
16+
17+
export { coReviewFactory, coReviewsFactory };

frontend/src/fixtures/response.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { createObjectFactory } from "./factory";
2+
3+
const FIXTURE_RESPONSE: Response = {
4+
headers: new Headers(),
5+
ok: true,
6+
redirected: false,
7+
status: 200,
8+
statusText: "OK",
9+
type: "basic",
10+
url: "",
11+
clone: function (): Response {
12+
throw new Error("Function not implemented.");
13+
},
14+
body: null,
15+
bodyUsed: false,
16+
arrayBuffer: function (): Promise<ArrayBuffer> {
17+
throw new Error("Function not implemented.");
18+
},
19+
blob: function (): Promise<Blob> {
20+
throw new Error("Function not implemented.");
21+
},
22+
formData: function (): Promise<FormData> {
23+
throw new Error("Function not implemented.");
24+
},
25+
json: function (): Promise<unknown> {
26+
throw new Error("Function not implemented.");
27+
},
28+
text: function (): Promise<string> {
29+
throw new Error("Function not implemented.");
30+
},
31+
};
32+
33+
const responseFactory = createObjectFactory<Response>(FIXTURE_RESPONSE);
34+
35+
export { responseFactory };

frontend/src/lib/api/coReview.test.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { expect } from "@storybook/test";
2+
3+
import { coReviewFactory } from "../../fixtures/coReview";
4+
import { destructionListFactory } from "../../fixtures/destructionList";
5+
import {
6+
addFetchMock,
7+
getLastMockedRequest,
8+
restoreNativeFetch,
9+
} from "../test";
10+
import { createCoReview, listCoReviews } from "./coReview";
11+
12+
describe("coReview", () => {
13+
afterEach(restoreNativeFetch);
14+
15+
test("listCoReviews() GETs co-reviews ", async () => {
16+
addFetchMock(
17+
"http://localhost:8000/api/v1/destruction-list-co-reviews/?",
18+
"GET",
19+
[coReviewFactory({ listFeedback: "gh-497" })],
20+
);
21+
const coReviews = await listCoReviews();
22+
expect(coReviews.length).toBe(1);
23+
expect(coReviews[0].listFeedback).toBe("gh-497");
24+
});
25+
26+
test("createCoReview() POSTs co-review ", async () => {
27+
addFetchMock(
28+
"http://localhost:8000/api/v1/destruction-list-co-reviews/?",
29+
"POST",
30+
{},
31+
);
32+
33+
const uuid = destructionListFactory().uuid;
34+
await createCoReview({
35+
destructionList: uuid,
36+
listFeedback: "gh-497",
37+
});
38+
39+
const requestInit = getLastMockedRequest(
40+
"http://localhost:8000/api/v1/destruction-list-co-reviews/?",
41+
"POST",
42+
);
43+
44+
const data = JSON.parse(requestInit?.body as string);
45+
expect(data.destructionList).toBe(uuid);
46+
expect(data.listFeedback).toBe("gh-497");
47+
});
48+
});

frontend/src/lib/api/coReview.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { User } from "./auth";
2+
import { DestructionList } from "./destructionLists";
3+
import { request } from "./request";
4+
5+
export type CoReviewBase = {
6+
destructionList: string;
7+
listFeedback: string;
8+
};
9+
10+
export type CoReview = CoReviewBase & {
11+
pk: number;
12+
author: User;
13+
created: string;
14+
};
15+
16+
/**
17+
* Create a new co-review.
18+
*/
19+
export async function createCoReview(data: CoReviewBase) {
20+
const response = await request(
21+
"POST",
22+
"/destruction-list-co-reviews/",
23+
undefined,
24+
{
25+
...data,
26+
},
27+
);
28+
const promise: Promise<unknown> = response.json();
29+
return promise;
30+
}
31+
32+
/**
33+
* List all the co-reviews that have been made for a destruction list.
34+
*/
35+
export async function listCoReviews(
36+
params?:
37+
| URLSearchParams
38+
| {
39+
destructionList__uuid?: DestructionList["uuid"];
40+
ordering?: "-created" | "created";
41+
},
42+
) {
43+
const response = await request(
44+
"GET",
45+
"/destruction-list-co-reviews/",
46+
params,
47+
);
48+
const promise: Promise<CoReview[]> = response.json();
49+
return promise;
50+
}

frontend/src/lib/test/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./mock";

frontend/src/lib/test/mock.ts

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { responseFactory } from "../../fixtures/response";
2+
3+
const SYMBOL_NATIVE_FETCH = Symbol();
4+
const SYMBOL_MOCKED_REQUESTS = Symbol();
5+
// @ts-expect-error - set native reference.
6+
window[SYMBOL_NATIVE_FETCH] = window.fetch;
7+
// @ts-expect-error - set mocked requests record.
8+
window[SYMBOL_MOCKED_REQUESTS] = {};
9+
10+
/**
11+
* Mock fetch() function.
12+
* This is useful when using Jest tests, use storybook-addon-mock for stories
13+
* (if available).
14+
*/
15+
export function addFetchMock(
16+
mockUrl: string,
17+
mockMethod: "DELETE" | "GET" | "PATCH" | "POST" | "PUT",
18+
responseData: unknown = {},
19+
) {
20+
window.fetch = async (...args) => {
21+
const [url, requestInit] = args;
22+
const method = requestInit?.method || "GET";
23+
24+
// Use mock if method and url match mock.
25+
if (url === mockUrl && method === mockMethod) {
26+
// Update window[SYMBOL_MOCKED_REQUESTS]
27+
// @ts-expect-error - set mocked requests record.
28+
window[SYMBOL_MOCKED_REQUESTS][url] = [
29+
// @ts-expect-error - get mocked requests record.
30+
...(window[SYMBOL_MOCKED_REQUESTS][url] || []),
31+
requestInit,
32+
];
33+
34+
// Return responseData
35+
return responseFactory({ json: async () => responseData });
36+
}
37+
38+
// We have mocks active (so testing environment) but are making live
39+
// requests. This is probably unwanted.
40+
// NOTE: Use e2e test to test actual integration.
41+
console.warn(method, url, "unmocked!");
42+
43+
// @ts-expect-error - get native reference.
44+
return window[SYMBOL_NATIVE_FETCH](...args);
45+
};
46+
}
47+
48+
/**
49+
* Restores original fetch function.
50+
*/
51+
export function restoreNativeFetch() {
52+
// @ts-expect-error - check native implementation
53+
if (window[SYMBOL_NATIVE_FETCH]) {
54+
// @ts-expect-error - set native implementation
55+
window.fetch = window[SYMBOL_NATIVE_FETCH];
56+
}
57+
}
58+
59+
/**
60+
* Returns calls to mocked URL.
61+
* @param mockUrl
62+
* @param mockMethod
63+
*/
64+
export function getMockedRequests(
65+
mockUrl: string,
66+
mockMethod: "DELETE" | "GET" | "PATCH" | "POST" | "PUT",
67+
): RequestInit[] {
68+
const records: RequestInit[] =
69+
// @ts-expect-error - get mocked requests records.
70+
window[SYMBOL_MOCKED_REQUESTS][mockUrl] || [];
71+
72+
return records.filter(
73+
(requestInit: RequestInit) => (requestInit.method || "GET") === mockMethod,
74+
);
75+
}
76+
77+
/**
78+
* Returns last call to mocked URL.
79+
* @param mockUrl
80+
* @param mockMethod
81+
*/
82+
export function getLastMockedRequest(
83+
mockUrl: string,
84+
mockMethod: "DELETE" | "GET" | "PATCH" | "POST" | "PUT",
85+
): RequestInit | undefined {
86+
const mockedRequests = getMockedRequests(mockUrl, mockMethod);
87+
return mockedRequests[mockedRequests.length - 1];
88+
}

0 commit comments

Comments
 (0)