Completed happy path for notification tests

main
Jackson Morgan 8 months ago
parent bb7a65b4a2
commit 7fb351fb0c
  1. 29
      packages/solid/src/requester/requests/deleteResource.ts
  2. 11
      packages/solid/src/requester/requests/readResource.ts
  3. 3
      packages/solid/src/resource/Leaf.ts
  4. 27
      packages/solid/src/resource/Resource.ts
  5. 2
      packages/solid/src/resource/notifications/NotificationMessage.ts
  6. 168
      packages/solid/test/Integration.test.ts

@ -62,15 +62,7 @@ export async function deleteResource(
// if it hasn't been deleted when you're unauthenticated. 404 happens when
// the document never existed
if (response.status === 205 || response.status === 404) {
if (options?.dataset) {
options.dataset.deleteMatches(
undefined,
undefined,
undefined,
namedNode(uri),
);
deleteResourceRdfFromContainer(uri, options.dataset);
}
updateDatasetOnSuccessfulDelete(uri, response.status === 205, options);
return {
isError: false,
type: "deleteSuccess",
@ -83,3 +75,22 @@ export async function deleteResource(
return UnexpectedResourceError.fromThrown(uri, err);
}
}
/**
* TODO
*/
export function updateDatasetOnSuccessfulDelete(
uri: string,
resourceExisted: boolean,
options?: DatasetRequestOptions,
): void {
if (options?.dataset) {
options.dataset.deleteMatches(
undefined,
undefined,
undefined,
namedNode(uri),
);
deleteResourceRdfFromContainer(uri, options.dataset);
}
}

@ -20,6 +20,7 @@ import { NoncompliantPodError } from "../results/error/NoncompliantPodError";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import { UnexpectedResourceError } from "../results/error/ErrorResult";
import { checkHeadersForRootContainer } from "./checkRootContainer";
import { namedNode } from "@rdfjs/data-model";
/**
* All possible return values for reading a leaf
@ -103,6 +104,16 @@ export async function readResource(
headers: { accept: "text/turtle, */*" },
});
if (response.status === 404) {
// Clear existing data if present
if (options?.dataset) {
options.dataset.deleteMatches(
undefined,
undefined,
undefined,
namedNode(uri),
);
}
return {
isError: false,
type: "absentReadSuccess",

@ -362,7 +362,8 @@ export class Leaf extends Resource {
* A helper method updates this leaf's internal state upon delete success
* @param result - the result of the delete success
*/
protected updateWithDeleteSuccess(_result: DeleteSuccess) {
public updateWithDeleteSuccess(result: DeleteSuccess) {
super.updateWithDeleteSuccess(result);
this.binaryData = undefined;
}

@ -15,7 +15,10 @@ import type TypedEmitter from "typed-emitter";
import EventEmitter from "events";
import { getParentUri } from "../util/rdfUtils";
import type { RequesterResult } from "../requester/results/RequesterResult";
import type { DeleteResult } from "../requester/requests/deleteResource";
import {
updateDatasetOnSuccessfulDelete,
type DeleteResult,
} from "../requester/requests/deleteResource";
import type { ReadSuccess } from "../requester/results/success/ReadSuccess";
import { isReadSuccess } from "../requester/results/success/ReadSuccess";
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
@ -427,7 +430,7 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
* A helper method updates this resource's internal state upon delete success
* @param result - the result of the delete success
*/
protected updateWithDeleteSuccess(_result: DeleteSuccess) {
public updateWithDeleteSuccess(_result: DeleteSuccess) {
this.absent = true;
this.didInitialFetch = true;
}
@ -743,9 +746,27 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
* TODO
*/
protected async onNotification(message: NotificationMessage): Promise<void> {
const objectResource = this.context.solidLdoDataset.getResource(
message.object,
);
switch (message.type) {
case "Update":
await this.read();
case "Add":
await objectResource.read();
return;
case "Delete":
case "Remove":
// Delete the resource without have to make an additional read request
updateDatasetOnSuccessfulDelete(message.object, true, {
dataset: this.context.solidLdoDataset,
});
objectResource.updateWithDeleteSuccess({
type: "deleteSuccess",
isError: false,
uri: message.object,
resourceExisted: true,
});
return;
}
}

@ -1,7 +1,7 @@
export interface NotificationMessage {
"@context": string | string[];
id: string;
type: "Update";
type: "Update" | "Delete" | "Remove" | "Add";
object: string;
published: string;
}

@ -2022,15 +2022,18 @@ describe("Integration", () => {
* ===========================================================================
*/
describe("Notification Subscriptions", () => {
it("Notification is propogated when a resource is updated", async () => {
const spidermanNode = namedNode("http://example.org/#spiderman");
const foafNameNode = namedNode("http://xmlns.com/foaf/0.1/name");
const spidermanNode = namedNode("http://example.org/#spiderman");
const foafNameNode = namedNode("http://xmlns.com/foaf/0.1/name");
it("handles notification when a resource is updated", async () => {
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
await resource.read();
const spidermanCallback = jest.fn();
solidLdoDataset.addListener([spidermanNode, null, null, null], jest.fn());
solidLdoDataset.addListener(
[spidermanNode, null, null, null],
spidermanCallback,
);
const subscriptionResult = await resource.subscribeToNotifications();
expect(subscriptionResult.type).toBe("subscribeToNotificationSuccess");
@ -2053,29 +2056,140 @@ describe("Integration", () => {
).toBe(1);
expect(spidermanCallback).toHaveBeenCalledTimes(1);
// // Notification is not propogated after unsubscribe
// spidermanCallback.mockClear();
// const unsubscribeResponse = await resource.unsubscribeFromNotifications();
// expect(unsubscribeResponse.type).toBe(
// "unsubscribeFromNotificationSuccess",
// );
// await authFetch(SAMPLE_DATA_URI, {
// method: "PATCH",
// body: 'INSERT DATA { <http://example.org/#spiderman> <http://xmlns.com/foaf/0.1/name> "Miles Morales" . }',
// headers: {
// "Content-Type": "application/sparql-update",
// },
// });
// await wait(50);
// expect(spidermanCallback).not.toHaveBeenCalled();
// expect(
// solidLdoDataset.match(
// spidermanNode,
// foafNameNode,
// literal("Miles Morales"),
// ).size,
// ).toBe(0);
// Notification is not propogated after unsubscribe
spidermanCallback.mockClear();
const unsubscribeResponse = await resource.unsubscribeFromNotifications();
expect(unsubscribeResponse.type).toBe(
"unsubscribeFromNotificationSuccess",
);
await authFetch(SAMPLE_DATA_URI, {
method: "PATCH",
body: 'INSERT DATA { <http://example.org/#spiderman> <http://xmlns.com/foaf/0.1/name> "Miles Morales" . }',
headers: {
"Content-Type": "application/sparql-update",
},
});
await wait(50);
expect(spidermanCallback).not.toHaveBeenCalled();
expect(
solidLdoDataset.match(
spidermanNode,
foafNameNode,
literal("Miles Morales"),
).size,
).toBe(0);
await resource.unsubscribeFromNotifications();
});
it("handles notification when subscribed to a child that is deleted", async () => {
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
const testContainer = solidLdoDataset.getResource(TEST_CONTAINER_URI);
await resource.read();
const spidermanCallback = jest.fn();
solidLdoDataset.addListener(
[spidermanNode, null, null, null],
spidermanCallback,
);
const containerCallback = jest.fn();
solidLdoDataset.addListener(
[namedNode(TEST_CONTAINER_URI), null, null, null],
containerCallback,
);
const subscriptionResult = await resource.subscribeToNotifications();
expect(subscriptionResult.type).toBe("subscribeToNotificationSuccess");
await authFetch(SAMPLE_DATA_URI, {
method: "DELETE",
});
await wait(1000);
expect(solidLdoDataset.match(spidermanNode, null, null).size).toBe(0);
expect(
testContainer.children().some((child) => child.uri === SAMPLE_DATA_URI),
).toBe(false);
expect(spidermanCallback).toHaveBeenCalledTimes(1);
expect(containerCallback).toHaveBeenCalledTimes(1);
await resource.unsubscribeFromNotifications();
});
it("handles notification when subscribed to a parent with a deleted child", async () => {
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
const testContainer = solidLdoDataset.getResource(TEST_CONTAINER_URI);
await resource.read();
const spidermanCallback = jest.fn();
solidLdoDataset.addListener(
[spidermanNode, null, null, null],
spidermanCallback,
);
const containerCallback = jest.fn();
solidLdoDataset.addListener(
[namedNode(TEST_CONTAINER_URI), null, null, null],
containerCallback,
);
const subscriptionResult = await testContainer.subscribeToNotifications();
expect(subscriptionResult.type).toBe("subscribeToNotificationSuccess");
await authFetch(SAMPLE_DATA_URI, {
method: "DELETE",
});
await wait(1000);
expect(solidLdoDataset.match(spidermanNode, null, null).size).toBe(0);
expect(
testContainer.children().some((child) => child.uri === SAMPLE_DATA_URI),
).toBe(false);
expect(spidermanCallback).toHaveBeenCalledTimes(1);
expect(containerCallback).toHaveBeenCalledTimes(1);
await testContainer.unsubscribeFromNotifications();
});
it("handles notification when subscribed to a parent with an added child", async () => {
const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI);
const testContainer = solidLdoDataset.getResource(TEST_CONTAINER_URI);
await resource.read();
const spidermanCallback = jest.fn();
solidLdoDataset.addListener(
[spidermanNode, null, null, null],
spidermanCallback,
);
const containerCallback = jest.fn();
solidLdoDataset.addListener(
[namedNode(TEST_CONTAINER_URI), null, null, null],
containerCallback,
);
const subscriptionResult = await testContainer.subscribeToNotifications();
expect(subscriptionResult.type).toBe("subscribeToNotificationSuccess");
await authFetch(TEST_CONTAINER_URI, {
method: "POST",
headers: { "content-type": "text/turtle", slug: "sample2.ttl" },
body: SPIDER_MAN_TTL,
});
await wait(1000);
expect(solidLdoDataset.match(spidermanNode, null, null).size).toBe(4);
expect(
testContainer
.children()
.some((child) => child.uri === SAMPLE2_DATA_URI),
).toBe(true);
expect(spidermanCallback).toHaveBeenCalledTimes(1);
expect(containerCallback).toHaveBeenCalledTimes(1);
await testContainer.unsubscribeFromNotifications();
});
});
});

Loading…
Cancel
Save