From 43376bced692e784e259d325a88d075cce686346 Mon Sep 17 00:00:00 2001 From: jaxoncreed Date: Sat, 16 Dec 2023 12:49:28 -0500 Subject: [PATCH] Added more integration tests --- packages/solid/src/SolidLdoDataset.ts | 25 +- packages/solid/src/createSolidLdoDataset.ts | 4 +- packages/solid/src/methods.ts | 6 +- .../src/requester/requests/uploadResource.ts | 1 - .../results/success/UpdateSuccess.ts | 4 + packages/solid/src/resource/Container.ts | 23 +- packages/solid/test/.ldo/post.context.ts | 31 ++ packages/solid/test/.ldo/post.schema.ts | 155 ++++++++ packages/solid/test/.ldo/post.shapeTypes.ts | 19 + packages/solid/test/.ldo/post.typings.ts | 45 +++ ...LdoDataset.test.ts => Integration.test.ts} | 372 +++++++++++++++++- 11 files changed, 655 insertions(+), 30 deletions(-) create mode 100644 packages/solid/test/.ldo/post.context.ts create mode 100644 packages/solid/test/.ldo/post.schema.ts create mode 100644 packages/solid/test/.ldo/post.shapeTypes.ts create mode 100644 packages/solid/test/.ldo/post.typings.ts rename packages/solid/test/{SolidLdoDataset.test.ts => Integration.test.ts} (70%) diff --git a/packages/solid/src/SolidLdoDataset.ts b/packages/solid/src/SolidLdoDataset.ts index 7dc4c2b..ea7371d 100644 --- a/packages/solid/src/SolidLdoDataset.ts +++ b/packages/solid/src/SolidLdoDataset.ts @@ -9,7 +9,10 @@ import type { import { AggregateError } from "./requester/results/error/ErrorResult"; import { InvalidUriError } from "./requester/results/error/InvalidUriError"; import type { AggregateSuccess } from "./requester/results/success/SuccessResult"; -import type { UpdateSuccess } from "./requester/results/success/UpdateSuccess"; +import type { + UpdateDefaultGraphSuccess, + UpdateSuccess, +} from "./requester/results/success/UpdateSuccess"; import type { Container } from "./resource/Container"; import type { Leaf } from "./resource/Leaf"; import type { ResourceResult } from "./resource/resourceResult/ResourceResult"; @@ -42,14 +45,18 @@ export class SolidLdoDataset extends LdoDataset { async commitChangesToPod( changes: DatasetChanges, ): Promise< - | AggregateSuccess> + | AggregateSuccess< + ResourceResult + > | AggregateError > { const changesByGraph = splitChangesByGraph(changes); + + // Iterate through all changes by graph in const results: [ GraphNode, DatasetChanges, - UpdateResult | InvalidUriError | { type: "defaultGraph"; isError: false }, + UpdateResult | InvalidUriError | UpdateDefaultGraphSuccess, ][] = await Promise.all( Array.from(changesByGraph.entries()).map( async ([graph, datasetChanges]) => { @@ -59,7 +66,10 @@ export class SolidLdoDataset extends LdoDataset { return [ graph, datasetChanges, - { type: "defaultGraph", isError: false }, + { + type: "updateDefaultGraphSuccess", + isError: false, + } as UpdateDefaultGraphSuccess, ]; } if (isContainerUri(graph.value)) { @@ -94,7 +104,8 @@ export class SolidLdoDataset extends LdoDataset { .map((result) => result[2]) .filter( (result): result is ResourceResult => - result.type === "updateSuccess", + result.type === "updateSuccess" || + result.type === "updateDefaultGraphSuccess", ), }; } @@ -111,8 +122,10 @@ export class SolidLdoDataset extends LdoDataset { createData( shapeType: ShapeType, subject: string | SubjectNode, - ...resources: Resource[] + resource: Resource, + ...additionalResources: Resource[] ): Type { + const resources = [resource, ...additionalResources]; const linkedDataObject = this.usingType(shapeType) .write(...resources.map((r) => r.uri)) .fromSubject(subject); diff --git a/packages/solid/src/createSolidLdoDataset.ts b/packages/solid/src/createSolidLdoDataset.ts index c7a268f..f9df1c7 100644 --- a/packages/solid/src/createSolidLdoDataset.ts +++ b/packages/solid/src/createSolidLdoDataset.ts @@ -2,9 +2,9 @@ import type { Dataset, DatasetFactory } from "@rdfjs/types"; import { SolidLdoDataset } from "./SolidLdoDataset"; import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext"; -import crossFetch from "cross-fetch"; import { createDataset, createDatasetFactory } from "@ldo/dataset"; import { ResourceStore } from "./ResourceStore"; +import { guaranteeFetch } from "./util/guaranteeFetch"; export interface CreateSolidLdoDatasetOptions { fetch?: typeof fetch; @@ -15,7 +15,7 @@ export interface CreateSolidLdoDatasetOptions { export function createSolidLdoDataset( options?: CreateSolidLdoDatasetOptions, ): SolidLdoDataset { - const finalFetch = options?.fetch || crossFetch; + const finalFetch = guaranteeFetch(options?.fetch); const finalDatasetFactory = options?.datasetFactory || createDatasetFactory(); const finalDataset = options?.dataset || createDataset(); diff --git a/packages/solid/src/methods.ts b/packages/solid/src/methods.ts index d58c48b..04fe6a5 100644 --- a/packages/solid/src/methods.ts +++ b/packages/solid/src/methods.ts @@ -4,6 +4,7 @@ import { write, transactionChanges, getDataset, + commitTransaction, } from "@ldo/ldo"; import type { DatasetChanges } from "@ldo/rdf-utils"; import type { Resource } from "./resource/Resource"; @@ -17,8 +18,10 @@ import type { Quad } from "@rdfjs/types"; */ export function changeData( input: Type, - ...resources: Resource[] + resource: Resource, + ...additionalResources: Resource[] ): Type { + const resources = [resource, ...additionalResources]; // Clone the input and set a graph const [transactionLdo] = write(...resources.map((r) => r.uri)).usingCopy( input, @@ -37,6 +40,7 @@ export function commitData( input: LdoBase, ): ReturnType { const changes = transactionChanges(input); + commitTransaction(input); const dataset = getDataset(input) as SolidLdoDataset; return dataset.commitChangesToPod(changes as DatasetChanges); } diff --git a/packages/solid/src/requester/requests/uploadResource.ts b/packages/solid/src/requester/requests/uploadResource.ts index 3d3eb9f..742ba7b 100644 --- a/packages/solid/src/requester/requests/uploadResource.ts +++ b/packages/solid/src/requester/requests/uploadResource.ts @@ -84,7 +84,6 @@ export async function uploadResource( }; } catch (err) { const thing = UnexpectedResourceError.fromThrown(uri, err); - console.log(thing.message); return thing; } } diff --git a/packages/solid/src/requester/results/success/UpdateSuccess.ts b/packages/solid/src/requester/results/success/UpdateSuccess.ts index 643ccc4..9bbea45 100644 --- a/packages/solid/src/requester/results/success/UpdateSuccess.ts +++ b/packages/solid/src/requester/results/success/UpdateSuccess.ts @@ -3,3 +3,7 @@ import type { ResourceSuccess } from "./SuccessResult"; export interface UpdateSuccess extends ResourceSuccess { type: "updateSuccess"; } + +export interface UpdateDefaultGraphSuccess extends ResourceSuccess { + type: "updateDefaultGraphSuccess"; +} diff --git a/packages/solid/src/resource/Container.ts b/packages/solid/src/resource/Container.ts index d83092d..f6ecc2b 100644 --- a/packages/solid/src/resource/Container.ts +++ b/packages/solid/src/resource/Container.ts @@ -114,28 +114,21 @@ export class Container extends Resource { } async getRootContainer(): Promise { - if (this.rootContainer === undefined) { - const checkResult = await this.checkIfIsRootContainer(); - if (checkResult.isError) return checkResult; - } - if (this.rootContainer === true) { + const parentContainerResult = await this.getParentContainer(); + if (parentContainerResult?.isError) return parentContainerResult; + if (!parentContainerResult) { return this; } - const parentUri = getParentUri(this.uri); - if (!parentUri) { - return new NoncompliantPodError( - this.uri, - "Resource does not have a root container", - ); - } - return this.context.resourceStore.get(parentUri).getRootContainer(); + return parentContainerResult.getRootContainer(); } async getParentContainer(): Promise< Container | CheckRootResultError | undefined > { - const checkResult = await this.checkIfIsRootContainer(); - if (checkResult.isError) return checkResult; + if (this.rootContainer === undefined) { + const checkResult = await this.checkIfIsRootContainer(); + if (checkResult.isError) return checkResult; + } if (this.rootContainer) return undefined; const parentUri = getParentUri(this.uri); if (!parentUri) { diff --git a/packages/solid/test/.ldo/post.context.ts b/packages/solid/test/.ldo/post.context.ts new file mode 100644 index 0000000..dafbe33 --- /dev/null +++ b/packages/solid/test/.ldo/post.context.ts @@ -0,0 +1,31 @@ +import { ContextDefinition } from "jsonld"; + +/** + * ============================================================================= + * postContext: JSONLD Context for post + * ============================================================================= + */ +export const postContext: ContextDefinition = { + type: { + "@id": "@type", + }, + SocialMediaPosting: "http://schema.org/SocialMediaPosting", + CreativeWork: "http://schema.org/CreativeWork", + Thing: "http://schema.org/Thing", + articleBody: { + "@id": "http://schema.org/articleBody", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + uploadDate: { + "@id": "http://schema.org/uploadDate", + "@type": "http://www.w3.org/2001/XMLSchema#date", + }, + image: { + "@id": "http://schema.org/image", + "@type": "@id", + }, + publisher: { + "@id": "http://schema.org/publisher", + "@type": "@id", + }, +}; diff --git a/packages/solid/test/.ldo/post.schema.ts b/packages/solid/test/.ldo/post.schema.ts new file mode 100644 index 0000000..39e8b63 --- /dev/null +++ b/packages/solid/test/.ldo/post.schema.ts @@ -0,0 +1,155 @@ +import { Schema } from "shexj"; + +/** + * ============================================================================= + * postSchema: ShexJ Schema for post + * ============================================================================= + */ +export const postSchema: Schema = { + type: "Schema", + shapes: [ + { + id: "https://example.com/PostSh", + type: "ShapeDecl", + shapeExpr: { + type: "Shape", + expression: { + type: "EachOf", + expressions: [ + { + type: "TripleConstraint", + predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + valueExpr: { + type: "NodeConstraint", + values: [ + "http://schema.org/SocialMediaPosting", + "http://schema.org/CreativeWork", + "http://schema.org/Thing", + ], + }, + }, + { + type: "TripleConstraint", + predicate: "http://schema.org/articleBody", + valueExpr: { + type: "NodeConstraint", + datatype: "http://www.w3.org/2001/XMLSchema#string", + }, + min: 0, + max: 1, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#label", + object: { + value: "articleBody", + }, + }, + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: "The actual body of the article. ", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://schema.org/uploadDate", + valueExpr: { + type: "NodeConstraint", + datatype: "http://www.w3.org/2001/XMLSchema#date", + }, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#label", + object: { + value: "uploadDate", + }, + }, + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: + "Date when this media object was uploaded to this site.", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://schema.org/image", + valueExpr: { + type: "NodeConstraint", + nodeKind: "iri", + }, + min: 0, + max: 1, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#label", + object: { + value: "image", + }, + }, + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: + "A media object that encodes this CreativeWork. This property is a synonym for encoding.", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://schema.org/publisher", + valueExpr: { + type: "NodeConstraint", + nodeKind: "iri", + }, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#label", + object: { + value: "publisher", + }, + }, + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: "The publisher of the creative work.", + }, + }, + ], + }, + ], + }, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#label", + object: { + value: "SocialMediaPost", + }, + }, + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: + "A post to a social media platform, including blog posts, tweets, Facebook posts, etc.", + }, + }, + ], + }, + }, + ], +}; diff --git a/packages/solid/test/.ldo/post.shapeTypes.ts b/packages/solid/test/.ldo/post.shapeTypes.ts new file mode 100644 index 0000000..4c50683 --- /dev/null +++ b/packages/solid/test/.ldo/post.shapeTypes.ts @@ -0,0 +1,19 @@ +import { ShapeType } from "@ldo/ldo"; +import { postSchema } from "./post.schema"; +import { postContext } from "./post.context"; +import { PostSh } from "./post.typings"; + +/** + * ============================================================================= + * LDO ShapeTypes post + * ============================================================================= + */ + +/** + * PostSh ShapeType + */ +export const PostShShapeType: ShapeType = { + schema: postSchema, + shape: "https://example.com/PostSh", + context: postContext, +}; diff --git a/packages/solid/test/.ldo/post.typings.ts b/packages/solid/test/.ldo/post.typings.ts new file mode 100644 index 0000000..9ebaf71 --- /dev/null +++ b/packages/solid/test/.ldo/post.typings.ts @@ -0,0 +1,45 @@ +import { ContextDefinition } from "jsonld"; + +/** + * ============================================================================= + * Typescript Typings for post + * ============================================================================= + */ + +/** + * PostSh Type + */ +export interface PostSh { + "@id"?: string; + "@context"?: ContextDefinition; + type: + | { + "@id": "SocialMediaPosting"; + } + | { + "@id": "CreativeWork"; + } + | { + "@id": "Thing"; + }; + /** + * The actual body of the article. + */ + articleBody?: string; + /** + * Date when this media object was uploaded to this site. + */ + uploadDate: string; + /** + * A media object that encodes this CreativeWork. This property is a synonym for encoding. + */ + image?: { + "@id": string; + }; + /** + * The publisher of the creative work. + */ + publisher: { + "@id": string; + }; +} diff --git a/packages/solid/test/SolidLdoDataset.test.ts b/packages/solid/test/Integration.test.ts similarity index 70% rename from packages/solid/test/SolidLdoDataset.test.ts rename to packages/solid/test/Integration.test.ts index 1a7ed69..aeb072a 100644 --- a/packages/solid/test/SolidLdoDataset.test.ts +++ b/packages/solid/test/Integration.test.ts @@ -7,23 +7,41 @@ import type { SolidLdoDataset, UpdateResultError, } from "../src"; -import { createSolidLdoDataset } from "../src"; +import { changeData, commitData, createSolidLdoDataset } from "../src"; import { ROOT_CONTAINER, createApp, getAuthenticatedFetch, } from "./solidServer.helper"; -import { namedNode, quad as createQuad, literal } from "@rdfjs/data-model"; +import { + namedNode, + quad as createQuad, + literal, + defaultGraph, +} 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 { + UpdateDefaultGraphSuccess, + 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 { + AggregateError, + UnexpectedResourceError, +} from "../src/requester/results/error/ErrorResult"; import type { InvalidUriError } from "../src/requester/results/error/InvalidUriError"; import { Buffer } from "buffer"; +import { PostShShapeType } from "./.ldo/post.shapeTypes"; +import type { + ServerHttpError, + UnauthenticatedHttpError, + UnexpectedHttpError, +} from "../src/requester/results/error/HttpErrorResult"; +import type { NoncompliantPodError } from "../src/requester/results/error/NoncompliantPodError"; const TEST_CONTAINER_SLUG = "test_ldo/"; const TEST_CONTAINER_URI = @@ -190,6 +208,28 @@ describe("SolidLdoDataset", () => { ).toBe(1); }); + it("Auto reads a resource", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI, { + autoLoad: true, + }); + // Wait until the resource is auto-loaded + await new Promise((resolve) => { + const interval = setInterval(() => { + if (!resource.isReading()) { + clearInterval(interval); + resolve(); + } + }, 250); + }); + expect( + solidLdoDataset.match( + namedNode("http://example.org/#spiderman"), + namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), + namedNode("http://example.org/#green-goblin"), + ).size, + ).toBe(1); + }); + it("Reads a container", async () => { const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); const result = await testRequestLoads(() => resource.read(), resource, { @@ -326,6 +366,88 @@ describe("SolidLdoDataset", () => { }); }); + /** + * readIfUnfetched + */ + describe("readIfUnfetched", () => { + it("reads an unfetched container", async () => { + const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => resource.readIfUnfetched(), + resource, + { + isLoading: true, + isReading: true, + }, + ); + expect(result.type).toBe("containerReadSuccess"); + expect(resource.children().length).toBe(2); + }); + + it("reads an unfetched leaf", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); + const result = await testRequestLoads( + () => resource.readIfUnfetched(), + resource, + { + isLoading: true, + isReading: true, + }, + ); + expect(result.type).toBe("dataReadSuccess"); + expect( + solidLdoDataset.match( + namedNode("http://example.org/#spiderman"), + namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), + namedNode("http://example.org/#green-goblin"), + ).size, + ).toBe(1); + }); + + it("returns a cached existing container", async () => { + const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); + await resource.read(); + fetchMock.mockClear(); + const result = await resource.readIfUnfetched(); + expect(fetchMock).not.toHaveBeenCalled(); + expect(result.type).toBe("containerReadSuccess"); + expect(resource.children().length).toBe(2); + }); + + it("returns a cached existing leaf", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI); + await resource.read(); + fetchMock.mockClear(); + const result = await resource.readIfUnfetched(); + expect(result.type).toBe("dataReadSuccess"); + expect( + solidLdoDataset.match( + namedNode("http://example.org/#spiderman"), + namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), + namedNode("http://example.org/#green-goblin"), + ).size, + ).toBe(1); + }); + + it("returns a cached absent container", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_CONTAINER_URI); + await resource.read(); + fetchMock.mockClear(); + const result = await resource.readIfUnfetched(); + expect(fetchMock).not.toHaveBeenCalled(); + expect(result.type).toBe("absentReadSuccess"); + }); + + it("returns a cached absent leaf", async () => { + const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); + await resource.read(); + fetchMock.mockClear(); + const result = await resource.readIfUnfetched(); + expect(fetchMock).not.toHaveBeenCalled(); + expect(result.type).toBe("absentReadSuccess"); + }); + }); + /** * Get Root Container */ @@ -336,6 +458,7 @@ describe("SolidLdoDataset", () => { expect(result.type).toBe("container"); if (result.type !== "container") return; expect(result.uri).toBe(ROOT_CONTAINER); + expect(result.isRootContainer()).toBe(true); }); it("Returns an error if there is no link header for a container request", async () => { @@ -376,6 +499,22 @@ describe("SolidLdoDataset", () => { expect(result.type).toBe("unexpectedResourceError"); expect(result.message).toBe("Something happened."); }); + + it("returns a NonCompliantPodError when there is no root", async () => { + fetchMock.mockResolvedValueOnce( + new Response(TEST_CONTAINER_TTL, { + status: 200, + headers: new Headers({ + "content-type": "text/turtle", + link: '; rel="type"', + }), + }), + ); + const resource = solidLdoDataset.getResource(ROOT_CONTAINER); + const result = await resource.getRootContainer(); + expect(result.isError).toBe(true); + expect(result.type).toBe("noncompliantPodError"); + }); }); /** @@ -520,7 +659,7 @@ describe("SolidLdoDataset", () => { const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI); const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); const result = await testRequestLoads( - () => resource.createAndOverwrite(), + () => resource.createIfAbsent(), resource, { isLoading: true, @@ -573,6 +712,38 @@ describe("SolidLdoDataset", () => { container.children().some((child) => child.uri === SAMPLE_DATA_URI), ).toBe(true); }); + + it("creates a container that doesn't exist", async () => { + const resource = solidLdoDataset.getResource(SAMPLE_CONTAINER_URI); + const container = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await testRequestLoads( + () => resource.createIfAbsent(), + 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 === SAMPLE_CONTAINER_URI), + ).toBe(true); + }); }); /** @@ -600,6 +771,76 @@ describe("SolidLdoDataset", () => { expect(result.isError).toBe(true); expect(result.type).toBe("unexpectedResourceError"); }); + + it("deletes a container", async () => { + const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); + const result = await resource.delete(); + expect(result.type === "deleteSuccess"); + }); + + it("returns an error on container read when deleting a container", async () => { + const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); + fetchMock.mockResolvedValueOnce( + new Response(SAMPLE_DATA_URI, { + status: 500, + }), + ); + const result = await resource.delete(); + expect(result.isError).toBe(true); + expect(result.type).toBe("aggregateError"); + const aggregateError = result as AggregateError< + | ServerHttpError + | UnexpectedHttpError + | UnauthenticatedHttpError + | UnexpectedResourceError + | NoncompliantPodError + >; + expect(aggregateError.errors[0].type).toBe("serverError"); + }); + + it("returns an error on child delete read when deleting a container", async () => { + const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); + fetchMock.mockImplementationOnce(authFetch); + fetchMock.mockResolvedValueOnce( + new Response(SAMPLE_DATA_URI, { + status: 500, + }), + ); + const result = await resource.delete(); + expect(result.isError).toBe(true); + expect(result.type).toBe("aggregateError"); + const aggregateError = result as AggregateError< + | ServerHttpError + | UnexpectedHttpError + | UnauthenticatedHttpError + | UnexpectedResourceError + | NoncompliantPodError + >; + expect(aggregateError.errors[0].type).toBe("serverError"); + }); + + it("returns an error on container delete read when deleting a container", async () => { + const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI); + fetchMock.mockImplementationOnce(authFetch); + fetchMock.mockImplementationOnce(authFetch); + fetchMock.mockImplementationOnce(authFetch); + fetchMock.mockResolvedValueOnce( + new Response(SAMPLE_DATA_URI, { + status: 500, + }), + ); + const result = await resource.delete(); + expect(result.isError).toBe(true); + expect(result.type).toBe("aggregateError"); + const aggregateError = result as AggregateError< + | ServerHttpError + | UnexpectedHttpError + | UnauthenticatedHttpError + | UnexpectedResourceError + | NoncompliantPodError + >; + expect(aggregateError.errors[0].type).toBe("serverError"); + }); }); /** @@ -670,6 +911,59 @@ describe("SolidLdoDataset", () => { expect(aggregateError.errors.length).toBe(1); expect(aggregateError.errors[0].type).toBe("unexpectedResourceError"); }); + + it("errors when trying to update a container", async () => { + 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_CONTAINER_URI), + ), + ]), + }; + 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 === "invalidUriError").toBe(true); + }); + + it("writes to the default graph without fetching", async () => { + const changes: DatasetChanges = { + added: createDataset([ + createQuad( + namedNode("http://example.org/#green-goblin"), + namedNode("http://xmlns.com/foaf/0.1/name"), + literal("Norman Osborn"), + defaultGraph(), + ), + ]), + }; + 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).toBe( + "updateDefaultGraphSuccess", + ); + expect( + solidLdoDataset.has( + createQuad( + namedNode("http://example.org/#green-goblin"), + namedNode("http://xmlns.com/foaf/0.1/name"), + literal("Norman Osborn"), + defaultGraph(), + ), + ), + ); + }); }); /** @@ -862,4 +1156,72 @@ describe("SolidLdoDataset", () => { ).toBe(true); }); }); + + /** + * =========================================================================== + * Methods + * =========================================================================== + */ + describe("methods", () => { + it("creates a data object for a specific subject", () => { + const resource = solidLdoDataset.getResource( + "https://example.com/resource.ttl", + ); + const post = solidLdoDataset.createData( + PostShShapeType, + "https://example.com/subject", + resource, + ); + post.type = { "@id": "CreativeWork" }; + expect(post.type["@id"]).toBe("CreativeWork"); + commitData(post); + expect( + solidLdoDataset.has( + createQuad( + namedNode("https://example.com/subject"), + namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + namedNode("http://schema.org/CreativeWork"), + namedNode("https://example.com/resource.ttl"), + ), + ), + ).toBe(true); + }); + + it("uses changeData to start a transaction", () => { + const resource = solidLdoDataset.getResource( + "https://example.com/resource.ttl", + ); + solidLdoDataset.add( + createQuad( + namedNode("https://example.com/subject"), + namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + namedNode("http://schema.org/CreativeWork"), + namedNode("https://example.com/resource.ttl"), + ), + ); + const post = solidLdoDataset + .usingType(PostShShapeType) + .fromSubject("https://example.com/subject"); + const cPost = changeData(post, resource); + cPost.type = { "@id": "SocialMediaPosting" }; + expect(cPost.type["@id"]).toBe("SocialMediaPosting"); + commitData(cPost); + expect( + solidLdoDataset.has( + createQuad( + namedNode("https://example.com/subject"), + namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + namedNode("http://schema.org/SocialMediaPosting"), + namedNode("https://example.com/resource.ttl"), + ), + ), + ).toBe(true); + }); + }); + + /** + * =========================================================================== + * Container-Specific Methods + * =========================================================================== + */ });