From f9996cf80f673b9eb0f934a4f2c15bd6caaaf082 Mon Sep 17 00:00:00 2001 From: jaxoncreed Date: Thu, 7 Dec 2023 17:20:20 -0500 Subject: [PATCH] Completed create and delete --- .../requester/requests/createDataResource.ts | 4 +- packages/solid/src/util/uriTypes.ts | 4 + packages/solid/test/SolidLdoDataset.test.ts | 387 +++++++++++++----- 3 files changed, 291 insertions(+), 104 deletions(-) diff --git a/packages/solid/src/requester/requests/createDataResource.ts b/packages/solid/src/requester/requests/createDataResource.ts index 66f4e76..b75386f 100644 --- a/packages/solid/src/requester/requests/createDataResource.ts +++ b/packages/solid/src/requester/requests/createDataResource.ts @@ -102,10 +102,12 @@ export async function createDataResource( > { 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); @@ -139,7 +141,7 @@ export async function createDataResource( isError: false, type: "createSuccess", uri, - didOverwrite: !!overwrite, + didOverwrite, }; } catch (err) { return UnexpectedResourceError.fromThrown(uri, err); diff --git a/packages/solid/src/util/uriTypes.ts b/packages/solid/src/util/uriTypes.ts index 77e1e81..24e4d58 100644 --- a/packages/solid/src/util/uriTypes.ts +++ b/packages/solid/src/util/uriTypes.ts @@ -1,5 +1,9 @@ +// The & {} allows for alias preservation +// eslint-disable-next-line @typescript-eslint/ban-types export type ContainerUri = `${string}/${NonPathnameEnding}` & {}; export type LeafUri = + // The & {} allows for alias preservation + // eslint-disable-next-line @typescript-eslint/ban-types `${string}${EveryLegalPathnameCharacterOtherThanSlash}${NonPathnameEnding}` & {}; export function isContainerUri(uri: string): uri is ContainerUri { diff --git a/packages/solid/test/SolidLdoDataset.test.ts b/packages/solid/test/SolidLdoDataset.test.ts index efd4360..1a122ff 100644 --- a/packages/solid/test/SolidLdoDataset.test.ts +++ b/packages/solid/test/SolidLdoDataset.test.ts @@ -1,5 +1,11 @@ import type { App } from "@solid/community-server"; -import type { ContainerUri, LeafUri, SolidLdoDataset } from "../src"; +import type { + Container, + ContainerUri, + Leaf, + LeafUri, + SolidLdoDataset, +} from "../src"; import { createSolidLdoDataset } from "../src"; import { ROOT_CONTAINER, @@ -7,7 +13,7 @@ import { getAuthenticatedFetch, } from "./solidServer.helper"; import { namedNode, quad as createQuad } from "@rdfjs/data-model"; -import { wait } from "./utils.helper"; +import type { CreateSuccess } from "../src/requester/results/success/CreateSuccess"; const TEST_CONTAINER_SLUG = "test_ldo/"; const TEST_CONTAINER_URI = @@ -16,6 +22,8 @@ const SAMPLE_DATA_URI = `${TEST_CONTAINER_URI}sample.ttl` as LeafUri; const SAMPLE2_DATA_URI = `${TEST_CONTAINER_URI}sample2.ttl` as LeafUri; const SAMPLE_BINARY_URI = `${TEST_CONTAINER_URI}sample.txt` as LeafUri; const SAMPLE2_BINARY_URI = `${TEST_CONTAINER_URI}sample2.txt` as LeafUri; +const SAMPLE_CONTAINER_URI = + `${TEST_CONTAINER_URI}sample_container/` as ContainerUri; const SPIDER_MAN_TTL = `@base . @prefix rdf: . @prefix rdfs: . @@ -50,6 +58,41 @@ const TEST_CONTAINER_TTL = `@prefix dc: . posix:mtime 1697810234; posix:size 10.`; +async function testRequestLoads( + request: () => Promise, + loadingResource: Leaf | Container, + loadingValues: Partial<{ + isLoading: boolean; + isCreating: boolean; + isReading: boolean; + isUploading: boolean; + isReloading: boolean; + isDeleting: boolean; + }>, +): Promise { + const allLoadingValues = { + isLoading: false, + isCreating: false, + isReading: false, + isUploading: false, + isReloading: false, + isDeleting: false, + ...loadingValues, + }; + const [returnVal] = await Promise.all([ + request(), + (async () => { + Object.entries(allLoadingValues).forEach(([key, value]) => { + if (loadingResource.type === "container" && key === "isUploading") { + return; + } + expect(loadingResource[key]()).toBe(value); + }); + })(), + ]); + return returnVal; +} + describe("SolidLdoDataset", () => { let app: App; let authFetch: typeof fetch; @@ -111,6 +154,9 @@ describe("SolidLdoDataset", () => { authFetch(SAMPLE2_BINARY_URI, { method: "DELETE", }), + authFetch(SAMPLE_CONTAINER_URI, { + method: "DELETE", + }), ]); }); @@ -120,7 +166,10 @@ describe("SolidLdoDataset", () => { describe("read", () => { it("Reads a data leaf", async () => { const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.type).toBe("dataReadSuccess"); expect( solidLdoDataset.match( @@ -133,14 +182,20 @@ describe("SolidLdoDataset", () => { it("Reads a container", async () => { const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.type).toBe("containerReadSuccess"); expect(resource.children().length).toBe(2); }); it("Reads a binary leaf", async () => { const resource = solidLdoDataset.getResource(SAMPLE_BINARY_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.type).toBe("binaryReadSuccess"); expect(resource.isBinary()).toBe(true); expect(await resource.getBlob()?.text()).toBe("some text."); @@ -148,7 +203,10 @@ describe("SolidLdoDataset", () => { it("Returns an absent result if the document doesn't exist", async () => { const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.type).toBe("absentReadSuccess"); if (result.type !== "absentReadSuccess") return; expect(result.resource.isAbsent()).toBe(true); @@ -157,7 +215,10 @@ describe("SolidLdoDataset", () => { it("Returns an ServerError when an 500 error is returned", async () => { fetchMock.mockResolvedValueOnce(new Response("Error", { status: 500 })); const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.isError).toBe(true); expect(result.type).toBe("serverError"); }); @@ -165,7 +226,10 @@ describe("SolidLdoDataset", () => { it("Returns an UnauthenticatedError on an 401 error is returned", async () => { fetchMock.mockResolvedValueOnce(new Response("Error", { status: 401 })); const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.isError).toBe(true); expect(result.type).toBe("unauthenticatedError"); }); @@ -173,7 +237,10 @@ describe("SolidLdoDataset", () => { it("Returns an UnexpectedHttpError on a strange number error is returned", async () => { fetchMock.mockResolvedValueOnce(new Response("Error", { status: 3942 })); const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.isError).toBe(true); expect(result.type).toBe("unexpectedHttpError"); }); @@ -183,7 +250,10 @@ describe("SolidLdoDataset", () => { new Response(undefined, { status: 200, headers: {} }), ); const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.isError).toBe(true); if (!result.isError) return; expect(result.type).toBe("noncompliantPodError"); @@ -200,7 +270,10 @@ describe("SolidLdoDataset", () => { }), ); const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.isError).toBe(true); if (!result.isError) return; expect(result.type).toBe("noncompliantPodError"); @@ -212,7 +285,10 @@ describe("SolidLdoDataset", () => { it("Returns an UnexpectedResourceError if an unknown error is triggered", async () => { fetchMock.mockRejectedValueOnce(new Error("Something happened.")); const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.isError).toBe(true); if (!result.isError) return; expect(result.type).toBe("unexpectedResourceError"); @@ -227,7 +303,10 @@ describe("SolidLdoDataset", () => { }), ); const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); - const result = await resource.read(); + const result = await testRequestLoads(() => resource.read(), resource, { + isLoading: true, + isReading: true, + }); expect(result.isError).toBe(true); if (!result.isError) return; expect(result.type).toBe("noncompliantPodError"); @@ -292,27 +371,154 @@ describe("SolidLdoDataset", () => { /** * Create Data Resource */ - describe("createDataResource", () => { + describe("createAndOverwrite", () => { it("creates a document that doesn't exist", async () => { const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => resource.createAndOverwrite(), + resource, + { + isLoading: true, + isCreating: 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_DATA_URI), + namedNode(TEST_CONTAINER_URI), + ), + ), + ).toBe(true); + expect( + container.children().some((child) => child.uri === SAMPLE2_DATA_URI), + ).toBe(true); + }); + + it("creates a data resource that doesn't exist while overwriting", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => resource.createAndOverwrite(), + resource, + { + isLoading: true, + isCreating: 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_DATA_URI), + namedNode(TEST_CONTAINER_URI), + ), + ), + ).toBe(true); + expect( + container.children().some((child) => child.uri === SAMPLE_DATA_URI), + ).toBe(true); + }); + + it("creates a container", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_CONTAINER_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => resource.createAndOverwrite(), + resource, + { + isLoading: true, + isCreating: 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(SAMPLE_CONTAINER_URI), + namedNode(TEST_CONTAINER_URI), + ), + ), + ).toBe(true); + expect( + container + .children() + .some((child) => child.uri === SAMPLE_CONTAINER_URI), + ).toBe(true); + }); + + it("returns a delete error if delete failed", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); + fetchMock.mockResolvedValueOnce( + new Response(TEST_CONTAINER_TTL, { + status: 500, + }), + ); + const result = await resource.createAndOverwrite(); + 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_DATA_URI); fetchMock.mockImplementationOnce(async (...args) => { - await wait(500); return authFetch(...args); }); - const [result] = await Promise.all([ - resource.createAndOverwrite(), - (async () => { - await wait(100); - expect(resource.isLoading()).toBe(true); - expect(resource.isCreating()).toBe(true); - expect(resource.isReading()).toBe(false); - expect(resource.isUploading()).toBe(false); - expect(resource.isReloading()).toBe(false); - expect(resource.isDeleting()).toBe(false); - })(), - ]); + fetchMock.mockResolvedValueOnce( + new Response(TEST_CONTAINER_TTL, { + status: 500, + }), + ); + const result = await resource.createAndOverwrite(); + 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_DATA_URI); + fetchMock.mockImplementationOnce(async (...args) => { + return authFetch(...args); + }); + fetchMock.mockImplementationOnce(async () => { + throw new Error("Some Unknown"); + }); + const result = await resource.createAndOverwrite(); + expect(result.isError).toBe(true); + expect(result.type).toBe("unexpectedResourceError"); + }); + }); + + describe("createIfAbsent", () => { + it("creates a data resource that doesn't exist", async () => { + const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => resource.createAndOverwrite(), + resource, + { + isLoading: true, + isCreating: true, + }, + ); + expect(result.type).toBe("createSuccess"); + const createSuccess = result as CreateSuccess; + expect(createSuccess.didOverwrite).toBe(false); expect( solidLdoDataset.has( createQuad( @@ -328,81 +534,56 @@ describe("SolidLdoDataset", () => { ).toBe(true); }); - // it("creates a data resource that doesn't exist while overwriting", async () => { - // const leafRequester = new LeafRequester( - // `${ROOT_COONTAINER}test_leaf/sample2.ttl`, - // solidLdoDataset.context, - // ); - // const result = await leafRequester.createDataResource(true); - // expect(result.type).toBe("data"); - // expect( - // solidLdoDataset.has( - // createQuad( - // namedNode(`${ROOT_COONTAINER}test_leaf/`), - // namedNode("http://www.w3.org/ns/ldp#contains"), - // namedNode(`${ROOT_COONTAINER}test_leaf/sample2.ttl`), - // namedNode(`${ROOT_COONTAINER}test_leaf/`), - // ), - // ), - // ).toBe(true); - // }); - - // it("creates a data resource that does exist while not overwriting", async () => { - // const leafRequester = new LeafRequester( - // `${ROOT_COONTAINER}test_leaf/sample.ttl`, - // solidLdoDataset.context, - // ); - // const result = await leafRequester.createDataResource(); - // expect(result.type).toBe("data"); - // expect( - // solidLdoDataset.has( - // createQuad( - // namedNode("http://example.org/#spiderman"), - // namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), - // namedNode("http://example.org/#green-goblin"), - // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), - // ), - // ), - // ).toBe(true); - // expect( - // solidLdoDataset.has( - // createQuad( - // namedNode(`${ROOT_COONTAINER}test_leaf/`), - // namedNode("http://www.w3.org/ns/ldp#contains"), - // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), - // namedNode(`${ROOT_COONTAINER}test_leaf/`), - // ), - // ), - // ).toBe(true); - // }); - - // it("creates a data resource that does exist while overwriting", async () => { - // const leafRequester = new LeafRequester( - // `${ROOT_COONTAINER}test_leaf/sample.ttl`, - // solidLdoDataset.context, - // ); - // const result = await leafRequester.createDataResource(true); - // expect(result.type).toBe("data"); - // expect( - // solidLdoDataset.has( - // createQuad( - // namedNode("http://example.org/#spiderman"), - // namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), - // namedNode("http://example.org/#green-goblin"), - // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), - // ), - // ), - // ).toBe(false); - // expect( - // solidLdoDataset.has( - // createQuad( - // namedNode(`${ROOT_COONTAINER}test_leaf/`), - // namedNode("http://www.w3.org/ns/ldp#contains"), - // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), - // namedNode(`${ROOT_COONTAINER}test_leaf/`), - // ), - // ), - // ).toBe(true); - // }); + it("doesn't overwrite a resources that does exist", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => resource.createIfAbsent(), + resource, + { + isLoading: true, + isCreating: true, + }, + ); + + expect(result.type).toBe("dataReadSuccess"); + expect( + solidLdoDataset.has( + createQuad( + namedNode(TEST_CONTAINER_URI), + namedNode("http://www.w3.org/ns/ldp#contains"), + namedNode(SAMPLE_DATA_URI), + namedNode(TEST_CONTAINER_URI), + ), + ), + ).toBe(true); + expect( + container.children().some((child) => child.uri === SAMPLE_DATA_URI), + ).toBe(true); + }); + }); + + describe("deleteResource", () => { + it("returns an unexpected http error if an unexpected value is returned", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); + fetchMock.mockResolvedValueOnce( + new Response(TEST_CONTAINER_TTL, { + status: 214, + }), + ); + const result = await resource.delete(); + expect(result.isError).toBe(true); + expect(result.type).toBe("unexpectedHttpError"); + }); + + it("returns an unexpected resource error if an unknown error is triggered", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); + fetchMock.mockImplementationOnce(async () => { + throw new Error("Some unknwon"); + }); + const result = await resource.delete(); + expect(result.isError).toBe(true); + expect(result.type).toBe("unexpectedResourceError"); + }); }); });