diff options
| author | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-07-02 11:55:17 -0700 |
|---|---|---|
| committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-07-02 11:55:17 -0700 |
| commit | 6bf4b90c90f15f4ab60833bddf5b5756d1a6b1f6 (patch) | |
| tree | ed97e39ec77c5231ffd2c394493e68d00ddac5a4 /Homework/cs5260/consumer/t | |
| download | misc-undergrad-6bf4b90c90f15f4ab60833bddf5b5756d1a6b1f6.tar.gz misc-undergrad-6bf4b90c90f15f4ab60833bddf5b5756d1a6b1f6.zip | |
Diffstat (limited to 'Homework/cs5260/consumer/t')
| -rw-r--r-- | Homework/cs5260/consumer/t/ConsumerRequestProcessor.spec.ts | 200 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/t/DynamoWidgetDAO.spec.ts | 117 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/t/Loop.spec.ts | 48 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/t/S3WidgetDao.spec.ts | 115 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/t/S3WidgetKeyGenerator.spec.ts | 48 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/t/S3WidgetRequestRetriever.spec.ts | 172 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/t/SQSWidgetRequestRetriever.spec.ts | 95 | ||||
| -rw-r--r-- | Homework/cs5260/consumer/t/index.ts | 0 |
8 files changed, 795 insertions, 0 deletions
diff --git a/Homework/cs5260/consumer/t/ConsumerRequestProcessor.spec.ts b/Homework/cs5260/consumer/t/ConsumerRequestProcessor.spec.ts new file mode 100644 index 0000000..28d785b --- /dev/null +++ b/Homework/cs5260/consumer/t/ConsumerRequestProcessor.spec.ts @@ -0,0 +1,200 @@ +import { randomUUID } from "node:crypto"; +import { Widget, WidgetRequest, WidgetT } from "@/shared/schema"; +import { expect, mock, test } from "bun:test"; +import { ConsumerRequestProcessor } from "@/consumer/processor"; +import { VoidLogger } from "@/shared/t"; + +const widgetDAOFactory = (): any => { + return { + retrieve: mock(async (widget: WidgetT) => { + return widget; + }), + save: mock(async (widget: WidgetT) => { + return widget; + }), + delete: mock(async (widget: WidgetT) => { + return widget; + }), + }; +}; + +test("when create request then warns when already existant and not save", async () => { + const logger = new VoidLogger(); + logger.warn = mock((_s) => {}); + + const mockWidgetDAO = widgetDAOFactory(); + mockWidgetDAO.retrieve = mock(async (widget: WidgetT) => widget); + + const newWidgetRequest = WidgetRequest.parse({ + type: "create", + requestId: randomUUID(), + owner: "simponic", + widgetId: randomUUID(), + }); + const processor = new ConsumerRequestProcessor(mockWidgetDAO, logger); + + await processor.processRequest(newWidgetRequest); + + expect(mockWidgetDAO.retrieve).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.save).toHaveBeenCalledTimes(0); + expect(logger.warn).toHaveBeenCalledTimes(1); +}); + +test("when create request then saves in dao if not exist", async () => { + const logger = new VoidLogger(); + const mockWidgetDAO = widgetDAOFactory(); + mockWidgetDAO.retrieve = mock(async (_widget: WidgetT) => { + return undefined; + }); + + const newWidgetRequest = WidgetRequest.parse({ + type: "create", + requestId: randomUUID(), + owner: "simponic", + widgetId: randomUUID(), + }); + const processor = new ConsumerRequestProcessor(mockWidgetDAO, logger); + + await processor.processRequest(newWidgetRequest); + + expect(mockWidgetDAO.retrieve).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.save).toHaveBeenCalledTimes(1); +}); + +test("when delete request then fails and warns if not exists", async () => { + const logger = new VoidLogger(); + logger.warn = mock((_s) => {}); + + const mockWidgetDAO = widgetDAOFactory(); + mockWidgetDAO.retrieve = mock(async (_widget: WidgetT) => { + return undefined; + }); + + const deleteWidgetRequest = WidgetRequest.parse({ + type: "delete", + requestId: randomUUID(), + owner: "simponic", + widgetId: randomUUID(), + }); + const processor = new ConsumerRequestProcessor(mockWidgetDAO, logger); + + await processor.processRequest(deleteWidgetRequest); + + expect(mockWidgetDAO.retrieve).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.delete).toHaveBeenCalledTimes(0); + expect(logger.warn).toHaveBeenCalledTimes(1); +}); + +test("when delete request then success if exists", async () => { + const logger = new VoidLogger(); + + const mockWidgetDAO = widgetDAOFactory(); + mockWidgetDAO.retrieve = mock(async (widget: WidgetT) => { + return widget; + }); + + const deleteWidgetRequest = WidgetRequest.parse({ + type: "delete", + requestId: randomUUID(), + owner: "simponic", + widgetId: randomUUID(), + }); + const processor = new ConsumerRequestProcessor(mockWidgetDAO, logger); + + await processor.processRequest(deleteWidgetRequest); + + expect(mockWidgetDAO.retrieve).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.delete).toHaveBeenCalledTimes(1); +}); + +test("when update request then deletes empty string values", async () => { + const logger = new VoidLogger(); + + const testWidget = Widget.parse({ + id: "test-widget", + owner: "simponic", + label: "current label", + }); + const mockWidgetDAO = widgetDAOFactory(); + mockWidgetDAO.retrieve = mock(async (_widget: WidgetT) => testWidget); + + const updateWidgetRequest = WidgetRequest.parse({ + type: "update", + requestId: randomUUID(), + owner: "simponic", + widgetId: testWidget.id, + label: "", + }); + + const processor = new ConsumerRequestProcessor(mockWidgetDAO, logger); + + await processor.processRequest(updateWidgetRequest); + + expect(mockWidgetDAO.retrieve).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.save).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.save.mock.calls[0][0]).toEqual( + Widget.parse({ id: "test-widget", owner: "simponic" }), // "label" has been removed + ); +}); + +test("when update attempts to overwrite owner then update fails and warns", async () => { + const logger = new VoidLogger(); + logger.warn = mock((_s) => {}); + + const testWidget = Widget.parse({ + id: "test-widget", + owner: "simponic", + label: "current label", + }); + + const mockWidgetDAO = widgetDAOFactory(); + mockWidgetDAO.retrieve = mock(async (_widget: WidgetT) => testWidget); + + const newWidgetRequest = WidgetRequest.parse({ + type: "update", + requestId: randomUUID(), + owner: "newOwner", + widgetId: testWidget.id, + }); + const processor = new ConsumerRequestProcessor(mockWidgetDAO, logger); + + await processor.processRequest(newWidgetRequest); + + expect(mockWidgetDAO.retrieve).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.save).toHaveBeenCalledTimes(0); + expect(logger.warn).toHaveBeenCalledTimes(1); +}); + +test("when update mutable attribute field unspecified then not overwritten", async () => { + const logger = new VoidLogger(); + + const testWidget = Widget.parse({ + id: "test-widget", + owner: "simponic", + label: "non-overwritten label", + }); + + const mockWidgetDAO = widgetDAOFactory(); + mockWidgetDAO.retrieve = mock(async (_widget: WidgetT) => testWidget); + + const newWidgetRequest = WidgetRequest.parse({ + type: "update", + requestId: randomUUID(), + owner: testWidget.owner, + widgetId: testWidget.id, + description: "", + }); + const processor = new ConsumerRequestProcessor(mockWidgetDAO, logger); + + await processor.processRequest(newWidgetRequest); + + expect(mockWidgetDAO.retrieve).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.save).toHaveBeenCalledTimes(1); + expect(mockWidgetDAO.save.mock.calls[0][0]).toEqual( + Widget.parse({ + id: "test-widget", + owner: "simponic", + label: "non-overwritten label", + }), + ); +}); diff --git a/Homework/cs5260/consumer/t/DynamoWidgetDAO.spec.ts b/Homework/cs5260/consumer/t/DynamoWidgetDAO.spec.ts new file mode 100644 index 0000000..01ea185 --- /dev/null +++ b/Homework/cs5260/consumer/t/DynamoWidgetDAO.spec.ts @@ -0,0 +1,117 @@ +import { Widget } from "@/shared/schema"; +import { expect, test, mock } from "bun:test"; +import { + DeleteItemCommand, + DynamoDBClient, + GetItemCommand, + PutItemCommand, +} from "@aws-sdk/client-dynamodb"; +import { DynamoWidgetDAO } from "@/consumer/dao"; +import { VoidLogger } from "@/shared/t"; + +const testTable = "widgets-t"; +const testWidget = Widget.parse({ + id: "testWidgetId", + owner: "simponic", + label: "A Widget", + description: "Nothing to see here!", + otherAttributes: [{ name: "length", value: "20cm" }], +}); + +const mockGetItemSend = mock(async (req: GetItemCommand) => { + if (req.input.Key!.id.S! === testWidget.id) { + return { + Item: { + id: { S: testWidget.id }, + owner: { S: testWidget.owner }, + label: { S: testWidget.label }, + description: { S: testWidget.description }, + length: { S: "20cm" }, + }, + }; + } + return {}; +}); + +const expectMockPutItem = (field: string, value: string) => + mock(async (req: PutItemCommand) => { + if (req.input.Item![field].S === value) { + return { Attributes: true }; + } + throw new Error("invalid field"); + }); + +const mockDeleteItemSuccessful = mock(async (req: DeleteItemCommand) => { + if (req.input!.Key!.id.S === testWidget.id) { + return { $metadata: true }; + } +}); + +test("when retrieve dynamo dao item then inflates and returns retrieved item", async () => { + const mockClient = { send: mockGetItemSend }; + const logger = new VoidLogger(); + const dao = new DynamoWidgetDAO( + testTable, + mockClient as unknown as DynamoDBClient, + logger, + ); + + const resp = await dao.retrieve(testWidget); + expect(resp).toEqual(testWidget); +}); + +test("when retrieve dynamo dao item not exist then returns undefined", async () => { + const mockClient = { send: mockGetItemSend }; + const logger = new VoidLogger(); + const dao = new DynamoWidgetDAO( + testTable, + mockClient as unknown as DynamoDBClient, + logger, + ); + + const resp = await dao.retrieve({ + id: "this-widget-doesnt-exist", + owner: "simponic", + }); + + expect(resp).toBeUndefined(); +}); + +test("when dynamo dao save then inflates object and returns", async () => { + const mockClient = { + send: expectMockPutItem("new_attribute", "exists"), + }; + const logger = new VoidLogger(); + const dao = new DynamoWidgetDAO( + testTable, + mockClient as unknown as DynamoDBClient, + logger, + ); + + const newWidget = Widget.parse({ + ...testWidget, + otherAttributes: [...testWidget.otherAttributes!], + }); + newWidget.otherAttributes!.push({ name: "new_attribute", value: "exists" }); + + const resp = await dao.save(newWidget); + + expect(resp).toEqual(newWidget); +}); + +test("when dynamo dao delete then returns deleted ", async () => { + const mockClient = { + send: mockDeleteItemSuccessful, + }; + const logger = new VoidLogger(); + + const dao = new DynamoWidgetDAO( + testTable, + mockClient as unknown as DynamoDBClient, + logger, + ); + + const resp = await dao.delete(testWidget); + + expect(resp).toEqual(testWidget); +}); diff --git a/Homework/cs5260/consumer/t/Loop.spec.ts b/Homework/cs5260/consumer/t/Loop.spec.ts new file mode 100644 index 0000000..44ba517 --- /dev/null +++ b/Homework/cs5260/consumer/t/Loop.spec.ts @@ -0,0 +1,48 @@ +import { MainLoop } from "@/consumer/Loop"; +import { WidgetRequestProcessor } from "@/consumer/processor"; +import { WidgetRequestRetriever } from "@/consumer/retrievers"; +import { expect, mock, test } from "bun:test"; +import { VoidLogger } from "@/shared/t"; +import { WidgetRequest } from "@/shared/schema"; +import { randomUUID } from "node:crypto"; + +test("main loop exits after max poll attempts", async () => { + const logger = new VoidLogger(); + + const maxPollAttempts = 3; + const timesToGiveData = 5; + let timesGivenData = 0; + + const mockRetriever: WidgetRequestRetriever = { + refreshRequests: mock(async () => { + if (timesGivenData < timesToGiveData) { + timesGivenData++; + return [ + WidgetRequest.parse({ + type: "create", + owner: "simponic", + widgetId: randomUUID(), + requestId: randomUUID(), + }), + ]; + } + return []; + }), + }; + const mockProcessor: WidgetRequestProcessor = { + processRequest: mock(async (_r) => {}), + }; + + await MainLoop( + [mockRetriever], + [mockProcessor], + maxPollAttempts, + 200, + logger, + ); + + expect(mockRetriever.refreshRequests).toHaveBeenCalledTimes( + timesGivenData + maxPollAttempts, + ); + expect(mockProcessor.processRequest).toHaveBeenCalledTimes(timesGivenData); +}); diff --git a/Homework/cs5260/consumer/t/S3WidgetDao.spec.ts b/Homework/cs5260/consumer/t/S3WidgetDao.spec.ts new file mode 100644 index 0000000..5eb4602 --- /dev/null +++ b/Homework/cs5260/consumer/t/S3WidgetDao.spec.ts @@ -0,0 +1,115 @@ +import { Widget } from "@/shared/schema"; +import { expect, test, mock } from "bun:test"; +import { S3WidgetKeyGenerator } from "@/consumer/dao/keys"; +import { + DeleteObjectCommand, + GetObjectCommand, + PutObjectCommand, + S3Client, +} from "@aws-sdk/client-s3"; +import { S3WidgetDAO } from "@/consumer/dao"; +import { VoidLogger } from "@/shared/t"; + +const testBucket = "test-bucket"; +const testWidget = Widget.parse({ + id: "testWidgetId", + owner: "simponic", + label: "A Widget", + description: "Nothing to see here!", +}); + +const testKeyGenerator = new S3WidgetKeyGenerator().withPrefix("test-widgets"); + +const mockedGetSend = mock(async (req: GetObjectCommand) => { + if (req.input.Key == "test-widgets/simponic/testWidgetId") { + return { + Body: { + transformToString: () => { + return JSON.stringify(testWidget); + }, + }, + }; + } + return {}; +}); + +const mockedPutSendSuccessful = mock(async (req: PutObjectCommand) => { + if (req.input.Key == "test-widgets/simponic/testWidgetId") { + return { + ChecksumSHA256: "laksdjf-lkejfoaj", + }; + } +}); + +const mockedDeleteSendSuccessful = mock(async (req: DeleteObjectCommand) => { + if (req.input.Key == "test-widgets/simponic/testWidgetId") { + return { + $metadata: true, + }; + } +}); + +test("when given widget then retrieves from s3", async () => { + const mockedClient = { send: mockedGetSend }; + const logger = new VoidLogger(); + + const dao = new S3WidgetDAO( + testBucket, + mockedClient as unknown as S3Client, + testKeyGenerator, + logger, + ); + + const resp = await dao.retrieve({ id: "testWidgetId", owner: "simponic" }); + + expect(resp?.label).toEqual(testWidget.label); + expect(resp?.id).toEqual(testWidget.id); +}); + +test("when cannot retrieve widget then returns undefined", async () => { + const mockedClient = { send: mockedGetSend }; + const logger = new VoidLogger(); + + const dao = new S3WidgetDAO( + testBucket, + mockedClient as unknown as S3Client, + testKeyGenerator, + logger, + ); + + const resp = await dao.retrieve({ + id: "thisWidgetDoesNotExist", + owner: "simponic", + }); + + expect(resp).toBeUndefined(); +}); + +test("when given widget then puts in s3 and returns", async () => { + const mockedClient = { send: mockedPutSendSuccessful }; + const logger = new VoidLogger(); + const dao = new S3WidgetDAO( + testBucket, + mockedClient as unknown as S3Client, + testKeyGenerator, + logger, + ); + + const resp = await dao.save(testWidget); + expect(resp).toEqual(testWidget); +}); + +test("when given widget then deletes in s3 and returns", async () => { + const mockedClient = { send: mockedDeleteSendSuccessful }; + const logger = new VoidLogger(); + + const dao = new S3WidgetDAO( + testBucket, + mockedClient as unknown as S3Client, + testKeyGenerator, + logger, + ); + + const resp = await dao.delete(testWidget); + expect(resp).toEqual(testWidget); +}); diff --git a/Homework/cs5260/consumer/t/S3WidgetKeyGenerator.spec.ts b/Homework/cs5260/consumer/t/S3WidgetKeyGenerator.spec.ts new file mode 100644 index 0000000..642c7a4 --- /dev/null +++ b/Homework/cs5260/consumer/t/S3WidgetKeyGenerator.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from "bun:test"; +import { S3WidgetKeyGenerator } from "@/consumer/dao/keys"; +import { Widget } from "@/shared/schema"; + +test("builds keys correctly", () => { + const generator = new S3WidgetKeyGenerator() + .withPrefix("test") + .withWidgetId("test") + .withOwner("simponic"); + + expect(generator.build()).toEqual("test/simponic/test"); +}); + +test("when owner given spaces then replaces with dashes and lowercases", () => { + const generator = new S3WidgetKeyGenerator() + .withPrefix("widgets") + .withWidgetId("test") + .withOwner("simPOnic test"); + + expect(generator.build()).toEqual("widgets/simponic-test/test"); +}); + +test("when not all specifications given then throws", () => { + const generator = new S3WidgetKeyGenerator() + .withPrefix("test") + .withWidgetId("test"); + + expect(() => generator.build()).toThrow(); +}); + +test("when invalid keys given then throws", () => { + const generator = new S3WidgetKeyGenerator() + .withPrefix("test") + .withOwner("simponic/simponic") + .withWidgetId("test"); + + expect(() => generator.build()).toThrow(); +}); + +test("when keys correct then builds from widget", () => { + const generator = new S3WidgetKeyGenerator().withPrefix("test-widgets"); + + const testWidget = Widget.parse({ owner: "simponic", id: "test-id" }); + + expect(generator.fromWidget(testWidget)).toEqual( + "test-widgets/simponic/test-id" + ); +}); diff --git a/Homework/cs5260/consumer/t/S3WidgetRequestRetriever.spec.ts b/Homework/cs5260/consumer/t/S3WidgetRequestRetriever.spec.ts new file mode 100644 index 0000000..9807875 --- /dev/null +++ b/Homework/cs5260/consumer/t/S3WidgetRequestRetriever.spec.ts @@ -0,0 +1,172 @@ +import { S3WidgetRequestRetriever } from "@/consumer/retrievers"; +import { WidgetRequest } from "@/shared/schema"; +import { + DeleteObjectCommand, + GetObjectCommand, + ListObjectsV2Command, + S3Client, +} from "@aws-sdk/client-s3"; +import { expect, mock, test } from "bun:test"; +import { randomUUID } from "node:crypto"; +import { VoidLogger } from "@/shared/t"; + +test("when widget request in S3 then reads, deletes, and returns", async () => { + const logger = new VoidLogger(); + const widgetRequest = WidgetRequest.parse({ + type: "create", + requestId: randomUUID(), + owner: "simponic", + widgetId: randomUUID(), + }); + + const key = "test-key"; + + const deleteKeyMock = mock(() => true); + const listKeyMock = mock(() => { + return { + Contents: [{ Key: key }], + }; + }); + const getKeyMock = mock(() => { + return { + Body: { + transformToString: async () => JSON.stringify(widgetRequest), + }, + }; + }); + + const mockS3Client = { + send: async ( + req: ListObjectsV2Command | DeleteObjectCommand | GetObjectCommand, + ) => { + if (req instanceof ListObjectsV2Command) { + return listKeyMock(); + } + + if (req instanceof DeleteObjectCommand) { + return deleteKeyMock(); + } + + if (req instanceof GetObjectCommand) { + if (req.input.Key === key) { + return getKeyMock(); + } + } + }, + }; + + const retriever = new S3WidgetRequestRetriever( + "test-bucket", + mockS3Client as unknown as S3Client, + logger, + ); + + expect(await retriever.refreshRequests()).toEqual([widgetRequest]); + expect(getKeyMock).toHaveBeenCalledTimes(1); + expect(listKeyMock).toHaveBeenCalledTimes(1); + expect(deleteKeyMock).toHaveBeenCalledTimes(1); +}); + +test("when empty widget request in S3 then deletes and returns empty", async () => { + const logger = new VoidLogger(); + const key = "test-key"; + + const deleteKeyMock = mock(() => true); + const listKeyMock = mock(() => { + return { + Contents: [{ Key: key }], + }; + }); + const getKeyMock = mock(() => { + return { + Body: { + transformToString: async () => "", + }, + }; + }); + + const mockS3Client = { + send: mock( + async ( + req: ListObjectsV2Command | DeleteObjectCommand | GetObjectCommand, + ) => { + if (req instanceof ListObjectsV2Command) { + return listKeyMock(); + } + + if (req instanceof DeleteObjectCommand) { + return deleteKeyMock(); + } + + if (req instanceof GetObjectCommand) { + if (req.input.Key === key) { + return getKeyMock(); + } + } + }, + ), + }; + + const retriever = new S3WidgetRequestRetriever( + "test-bucket", + mockS3Client as unknown as S3Client, + logger, + ); + + expect(await retriever.refreshRequests()).toBeEmpty(); + expect(getKeyMock).toHaveBeenCalledTimes(1); + expect(listKeyMock).toHaveBeenCalledTimes(1); + expect(deleteKeyMock).toHaveBeenCalledTimes(1); +}); + +test("when no keys then returns empty", async () => { + const logger = new VoidLogger(); + const key = "test-key"; + + const deleteKeyMock = mock(() => true); + const listKeyMock = mock(() => { + return { + Contents: [], + }; + }); + const getKeyMock = mock(() => { + return { + Body: { + transformToString: async () => "", + }, + }; + }); + + const mockS3Client = { + send: mock( + async ( + req: ListObjectsV2Command | DeleteObjectCommand | GetObjectCommand, + ) => { + if (req instanceof ListObjectsV2Command) { + return listKeyMock(); + } + + if (req instanceof DeleteObjectCommand) { + return deleteKeyMock(); + } + + if (req instanceof GetObjectCommand) { + if (req.input.Key === key) { + return getKeyMock(); + } + } + }, + ), + }; + + const retriever = new S3WidgetRequestRetriever( + "test-bucket", + mockS3Client as unknown as S3Client, + logger, + ); + + expect(await retriever.refreshRequests()).toEqual([]); + expect(listKeyMock).toHaveBeenCalledTimes(1); + expect(getKeyMock).toHaveBeenCalledTimes(0); + expect(deleteKeyMock).toHaveBeenCalledTimes(0); +}); diff --git a/Homework/cs5260/consumer/t/SQSWidgetRequestRetriever.spec.ts b/Homework/cs5260/consumer/t/SQSWidgetRequestRetriever.spec.ts new file mode 100644 index 0000000..cae6a65 --- /dev/null +++ b/Homework/cs5260/consumer/t/SQSWidgetRequestRetriever.spec.ts @@ -0,0 +1,95 @@ +import { SQSWidgetRequestRetriever } from "@/consumer/retrievers"; +import { WidgetRequest } from "@/shared/schema"; +import { + DeleteMessageBatchCommand, + ReceiveMessageCommand, + SQSClient, +} from "@aws-sdk/client-sqs"; +import { expect, mock, test } from "bun:test"; +import { randomUUID } from "node:crypto"; +import { VoidLogger } from "@/shared/t"; + +test("when widget request in SQS then reads, deletes, and returns", async () => { + const logger = new VoidLogger(); + const widgetRequest = WidgetRequest.parse({ + type: "create", + requestId: randomUUID(), + owner: "simponic", + widgetId: randomUUID(), + }); + + const queueUrl = "https://asdf.fdsa.asdf"; + + const deleteMessagesBatch = mock(() => true); + const getMessages = mock(() => { + return { + Messages: [ + { + Body: JSON.stringify(widgetRequest), + MessageId: "fdsa", + ReceiptHandle: "asdf", + }, + ], + }; + }); + + const mockSQSClient = { + send: async (req: ReceiveMessageCommand | DeleteMessageBatchCommand) => { + if (req instanceof ReceiveMessageCommand) { + if (req.input.QueueUrl === queueUrl) return getMessages(); + } + if (req instanceof DeleteMessageBatchCommand) { + expect( + req.input.Entries?.find( + (x) => x.Id === "fdsa" && x.ReceiptHandle === "asdf", + ), + ).toBeTruthy(); + + if (req.input.QueueUrl === queueUrl) return deleteMessagesBatch(); + } + return; + }, + }; + + const retriever = new SQSWidgetRequestRetriever( + queueUrl, + mockSQSClient as unknown as SQSClient, + logger, + ); + + expect(await retriever.refreshRequests()).toEqual([widgetRequest]); + expect(deleteMessagesBatch).toHaveBeenCalledTimes(1); + expect(getMessages).toHaveBeenCalledTimes(1); +}); + +test("when empty widget request in SQS then returns empty", async () => { + const logger = new VoidLogger(); + const queueUrl = "https://asdf.fdsa.asdf"; + + const getMessages = mock(() => { + return {}; + }); + const deleteMessagesBatch = mock(() => true); + + const mockSQSClient = { + send: async (req: ReceiveMessageCommand | DeleteMessageBatchCommand) => { + if (req instanceof ReceiveMessageCommand) { + if (req.input.QueueUrl === queueUrl) return getMessages(); + } + if (req instanceof DeleteMessageBatchCommand) { + if (req.input.QueueUrl === queueUrl) return deleteMessagesBatch(); + } + return; + }, + }; + + const retriever = new SQSWidgetRequestRetriever( + queueUrl, + mockSQSClient as unknown as SQSClient, + logger, + ); + + expect(await retriever.refreshRequests()).toBeEmpty(); + expect(getMessages).toHaveBeenCalledTimes(1); + expect(deleteMessagesBatch).toHaveBeenCalledTimes(0); +}); diff --git a/Homework/cs5260/consumer/t/index.ts b/Homework/cs5260/consumer/t/index.ts new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Homework/cs5260/consumer/t/index.ts |
