From c0d32e983fd0226aef3056c13a89e21a83728ba7 Mon Sep 17 00:00:00 2001 From: jaxoncreed Date: Fri, 8 Dec 2023 23:19:11 -0500 Subject: [PATCH] Complete main request tests --- package-lock.json | 112 +++++++ .../src/requester/requests/uploadResource.ts | 8 +- packages/solid/test/SolidLdoDataset.test.ts | 280 +++++++++++++++++- 3 files changed, 396 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index a73a107..3e4b48b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11791,6 +11791,12 @@ "node": ">= 6" } }, + "node_modules/blob-polyfill": { + "version": "7.0.20220408", + "resolved": "https://registry.npmjs.org/blob-polyfill/-/blob-polyfill-7.0.20220408.tgz", + "integrity": "sha512-oD8Ydw+5lNoqq+en24iuPt1QixdPpe/nUF8azTHnviCZYu9zUC+TwdzIp5orpblJosNlgNbVmmAb//c6d6ImUQ==", + "dev": true + }, "node_modules/bluebird": { "version": "3.7.2", "license": "MIT" @@ -14122,6 +14128,42 @@ "devOptional": true, "license": "MIT" }, + "node_modules/cross-blob": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/cross-blob/-/cross-blob-3.0.2.tgz", + "integrity": "sha512-u+7xq68MAjIqvoEKrdgIEupKJNBeU8MSl/cpfPmJ3rm9yvxrgbMPr8TkZS9qnwCgiVC8BsEt9kDkeD7He2zmNA==", + "dev": true, + "dependencies": { + "blob-polyfill": "^7.0.20220408", + "fetch-blob": "^3.2.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/cross-blob/node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/cross-fetch": { "version": "3.1.8", "license": "MIT", @@ -23733,6 +23775,25 @@ "node": ">= 0.10.5" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "license": "MIT", @@ -31714,6 +31775,15 @@ "defaults": "^1.0.3" } }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/web-streams-ponyfill": { "version": "1.4.2", "dev": true, @@ -34809,6 +34879,7 @@ "@rdfjs/types": "^1.0.1", "@solid/community-server": "^6.0.2", "@types/jest": "^29.0.3", + "cross-blob": "^3.0.2", "dotenv": "^16.3.1", "jest-rdf": "^1.8.0", "ts-jest": "^29.0.2", @@ -44803,6 +44874,7 @@ "@solid/community-server": "^6.0.2", "@types/jest": "^29.0.3", "@types/parse-link-header": "^2.0.1", + "cross-blob": "*", "cross-fetch": "^3.1.6", "dotenv": "^16.3.1", "http-link-header": "^1.1.1", @@ -50223,6 +50295,12 @@ } } }, + "blob-polyfill": { + "version": "7.0.20220408", + "resolved": "https://registry.npmjs.org/blob-polyfill/-/blob-polyfill-7.0.20220408.tgz", + "integrity": "sha512-oD8Ydw+5lNoqq+en24iuPt1QixdPpe/nUF8azTHnviCZYu9zUC+TwdzIp5orpblJosNlgNbVmmAb//c6d6ImUQ==", + "dev": true + }, "bluebird": { "version": "3.7.2" }, @@ -51762,6 +51840,28 @@ "version": "1.1.1", "devOptional": true }, + "cross-blob": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/cross-blob/-/cross-blob-3.0.2.tgz", + "integrity": "sha512-u+7xq68MAjIqvoEKrdgIEupKJNBeU8MSl/cpfPmJ3rm9yvxrgbMPr8TkZS9qnwCgiVC8BsEt9kDkeD7He2zmNA==", + "dev": true, + "requires": { + "blob-polyfill": "^7.0.20220408", + "fetch-blob": "^3.2.0" + }, + "dependencies": { + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + } + } + }, "cross-fetch": { "version": "3.1.8", "requires": { @@ -57863,6 +57963,12 @@ "minimatch": "^3.0.2" } }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true + }, "node-fetch": { "version": "2.6.7", "requires": { @@ -62882,6 +62988,12 @@ "defaults": "^1.0.3" } }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true + }, "web-streams-ponyfill": { "version": "1.4.2", "dev": true diff --git a/packages/solid/src/requester/requests/uploadResource.ts b/packages/solid/src/requester/requests/uploadResource.ts index 3ef39a3..3d3eb9f 100644 --- a/packages/solid/src/requester/requests/uploadResource.ts +++ b/packages/solid/src/requester/requests/uploadResource.ts @@ -45,10 +45,12 @@ export async function uploadResource( ): Promise { try { const fetch = guaranteeFetch(options?.fetch); + let didOverwrite = false; if (overwrite) { const deleteResult = await deleteResource(uri, options); // Return if it wasn't deleted if (deleteResult.isError) return deleteResult; + didOverwrite = deleteResult.resourceExisted; } else { // Perform a read to check if it exists const readResult = await readResource(uri, options); @@ -78,9 +80,11 @@ export async function uploadResource( isError: false, type: "createSuccess", uri, - didOverwrite: !!overwrite, + didOverwrite, }; } catch (err) { - return UnexpectedResourceError.fromThrown(uri, err); + const thing = UnexpectedResourceError.fromThrown(uri, err); + console.log(thing.message); + return thing; } } diff --git a/packages/solid/test/SolidLdoDataset.test.ts b/packages/solid/test/SolidLdoDataset.test.ts index 1a122ff..1a7ed69 100644 --- a/packages/solid/test/SolidLdoDataset.test.ts +++ b/packages/solid/test/SolidLdoDataset.test.ts @@ -5,6 +5,7 @@ import type { Leaf, LeafUri, SolidLdoDataset, + UpdateResultError, } from "../src"; import { createSolidLdoDataset } from "../src"; import { @@ -12,8 +13,17 @@ import { createApp, getAuthenticatedFetch, } from "./solidServer.helper"; -import { namedNode, quad as createQuad } from "@rdfjs/data-model"; +import { namedNode, quad as createQuad, literal } from "@rdfjs/data-model"; import type { CreateSuccess } from "../src/requester/results/success/CreateSuccess"; +import type { DatasetChanges } from "@ldo/rdf-utils"; +import { createDataset } from "@ldo/dataset"; +import type { Quad } from "@rdfjs/types"; +import type { AggregateSuccess } from "../src/requester/results/success/SuccessResult"; +import type { UpdateSuccess } from "../src/requester/results/success/UpdateSuccess"; +import type { ResourceSuccess } from "../src/resource/resourceResult/ResourceResult"; +import type { AggregateError } from "../src/requester/results/error/ErrorResult"; +import type { InvalidUriError } from "../src/requester/results/error/InvalidUriError"; +import { Buffer } from "buffer"; const TEST_CONTAINER_SLUG = "test_ldo/"; const TEST_CONTAINER_URI = @@ -369,7 +379,9 @@ describe("SolidLdoDataset", () => { }); /** - * Create Data Resource + * =========================================================================== + * Create + * =========================================================================== */ describe("createAndOverwrite", () => { it("creates a document that doesn't exist", async () => { @@ -563,6 +575,9 @@ describe("SolidLdoDataset", () => { }); }); + /** + * Delete + */ describe("deleteResource", () => { it("returns an unexpected http error if an unexpected value is returned", async () => { const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); @@ -586,4 +601,265 @@ describe("SolidLdoDataset", () => { expect(result.type).toBe("unexpectedResourceError"); }); }); + + /** + * Update + */ + describe("updateDataResource", () => { + const changes: DatasetChanges = { + added: createDataset([ + createQuad( + namedNode("http://example.org/#green-goblin"), + namedNode("http://xmlns.com/foaf/0.1/name"), + literal("Norman Osborn"), + namedNode(SAMPLE_DATA_URI), + ), + ]), + removed: createDataset([ + createQuad( + namedNode("http://example.org/#green-goblin"), + namedNode("http://xmlns.com/foaf/0.1/name"), + literal("Green Goblin"), + namedNode(SAMPLE_DATA_URI), + ), + ]), + }; + + it("applies changes to a Pod", async () => { + const result = await solidLdoDataset.commitChangesToPod(changes); + expect(result.type).toBe("aggregateSuccess"); + const aggregateSuccess = result as AggregateSuccess< + ResourceSuccess + >; + expect(aggregateSuccess.results.length).toBe(1); + expect(aggregateSuccess.results[0].type === "updateSuccess").toBe(true); + expect( + solidLdoDataset.has( + createQuad( + namedNode("http://example.org/#green-goblin"), + namedNode("http://xmlns.com/foaf/0.1/name"), + literal("Norman Osborn"), + namedNode(SAMPLE_DATA_URI), + ), + ), + ); + }); + + it("handles an HTTP error", async () => { + fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 })); + const result = await solidLdoDataset.commitChangesToPod(changes); + expect(result.isError).toBe(true); + expect(result.type).toBe("aggregateError"); + const aggregateError = result as AggregateError< + UpdateResultError | InvalidUriError + >; + expect(aggregateError.errors.length).toBe(1); + expect(aggregateError.errors[0].type).toBe("serverError"); + }); + + it("handles an unknown request", async () => { + fetchMock.mockImplementationOnce(() => { + throw new Error("Some Error"); + }); + const result = await solidLdoDataset.commitChangesToPod(changes); + expect(result.isError).toBe(true); + expect(result.type).toBe("aggregateError"); + const aggregateError = result as AggregateError< + UpdateResultError | InvalidUriError + >; + expect(aggregateError.errors.length).toBe(1); + expect(aggregateError.errors[0].type).toBe("unexpectedResourceError"); + }); + }); + + /** + * =========================================================================== + * Upload + * =========================================================================== + */ + describe("uploadAndOverwrite", () => { + it("uploads a document that doesn't exist", async () => { + const resource = solidLdoDataset.getResource(SAMPLE2_BINARY_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => + resource.uploadAndOverwrite( + Buffer.from("some text.") as unknown as Blob, + "text/plain", + ), + resource, + { + isLoading: true, + isUploading: true, + }, + ); + + expect(result.type).toBe("createSuccess"); + const createSuccess = result as CreateSuccess; + expect(createSuccess.didOverwrite).toBe(false); + expect( + solidLdoDataset.has( + createQuad( + namedNode(TEST_CONTAINER_URI), + namedNode("http://www.w3.org/ns/ldp#contains"), + namedNode(SAMPLE2_BINARY_URI), + namedNode(TEST_CONTAINER_URI), + ), + ), + ).toBe(true); + expect( + container.children().some((child) => child.uri === SAMPLE2_BINARY_URI), + ).toBe(true); + }); + + it("creates a binary resource that doesn't exist while overwriting", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => + resource.uploadAndOverwrite( + Buffer.from("some text.") as unknown as Blob, + "text/plain", + ), + resource, + { + isLoading: true, + isUploading: true, + }, + ); + expect(result.type).toBe("createSuccess"); + const createSuccess = result as CreateSuccess; + expect(createSuccess.didOverwrite).toBe(true); + expect( + solidLdoDataset.has( + createQuad( + namedNode(TEST_CONTAINER_URI), + namedNode("http://www.w3.org/ns/ldp#contains"), + namedNode(SAMPLE_BINARY_URI), + namedNode(TEST_CONTAINER_URI), + ), + ), + ).toBe(true); + expect( + container.children().some((child) => child.uri === SAMPLE_BINARY_URI), + ).toBe(true); + }); + + it("returns a delete error if delete failed", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); + fetchMock.mockResolvedValueOnce( + new Response(TEST_CONTAINER_TTL, { + status: 500, + }), + ); + const result = await resource.uploadAndOverwrite( + Buffer.from("some text.") as unknown as Blob, + "text/plain", + ); + expect(result.isError).toBe(true); + expect(result.type).toBe("serverError"); + }); + + it("returns an error if the create fetch fails", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); + fetchMock.mockImplementationOnce(async (...args) => { + return authFetch(...args); + }); + fetchMock.mockResolvedValueOnce( + new Response(TEST_CONTAINER_TTL, { + status: 500, + }), + ); + const result = await resource.uploadAndOverwrite( + Buffer.from("some text.") as unknown as Blob, + "text/plain", + ); + expect(result.isError).toBe(true); + expect(result.type).toBe("serverError"); + }); + + it("returns an unexpected error if some unknown error is triggered", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); + fetchMock.mockImplementationOnce(async (...args) => { + return authFetch(...args); + }); + fetchMock.mockImplementationOnce(async () => { + throw new Error("Some Unknown"); + }); + const result = await resource.uploadAndOverwrite( + Buffer.from("some text.") as unknown as Blob, + "text/plain", + ); + expect(result.isError).toBe(true); + expect(result.type).toBe("unexpectedResourceError"); + }); + }); + + describe("uploadIfAbsent", () => { + it("creates a binary resource that doesn't exist", async () => { + const resource = solidLdoDataset.getResource(SAMPLE2_BINARY_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => + resource.uploadIfAbsent( + Buffer.from("some text.") as unknown as Blob, + "text/plain", + ), + resource, + { + isLoading: true, + isUploading: true, + }, + ); + + expect(result.type).toBe("createSuccess"); + const createSuccess = result as CreateSuccess; + expect(createSuccess.didOverwrite).toBe(false); + expect( + solidLdoDataset.has( + createQuad( + namedNode(TEST_CONTAINER_URI), + namedNode("http://www.w3.org/ns/ldp#contains"), + namedNode(SAMPLE2_BINARY_URI), + namedNode(TEST_CONTAINER_URI), + ), + ), + ).toBe(true); + expect( + container.children().some((child) => child.uri === SAMPLE2_BINARY_URI), + ).toBe(true); + }); + + it("doesn't overwrite a binary resource that does exist", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => + resource.uploadIfAbsent( + Buffer.from("some text.") as unknown as Blob, + "text/plain", + ), + resource, + { + isLoading: true, + isUploading: true, + }, + ); + + expect(result.type).toBe("binaryReadSuccess"); + expect( + solidLdoDataset.has( + createQuad( + namedNode(TEST_CONTAINER_URI), + namedNode("http://www.w3.org/ns/ldp#contains"), + namedNode(SAMPLE_BINARY_URI), + namedNode(TEST_CONTAINER_URI), + ), + ), + ).toBe(true); + expect( + container.children().some((child) => child.uri === SAMPLE_BINARY_URI), + ).toBe(true); + }); + }); });