Encountering errors in tests

main
Jackson Morgan 8 months ago
parent 69e1ffb963
commit 44575669e5
  1. 48
      package-lock.json
  2. 4
      packages/solid/package.json
  3. 7
      packages/solid/src/requester/requests/readResource.ts
  4. 83
      packages/solid/src/resource/Resource.ts
  5. 7
      packages/solid/src/resource/notifications/NotificationMessage.ts
  6. 35
      packages/solid/src/resource/notifications/NotificationSubscription.ts
  7. 61
      packages/solid/src/resource/notifications/Websocket2023NotificationSubscription.ts
  8. 5
      packages/solid/src/resource/notifications/results/NotificationErrors.ts
  9. 8
      packages/solid/src/resource/notifications/results/SubscribeToNotificationSuccess.ts
  10. 8
      packages/solid/src/resource/notifications/results/UnsubscribeFromNotificationSuccess.ts
  11. 72
      packages/solid/test/Integration.test.ts

48
package-lock.json generated

@ -1948,7 +1948,6 @@
},
"node_modules/@bergos/jsonparse": {
"version": "1.4.1",
"dev": true,
"engines": [
"node >= 0.2.0"
],
@ -1959,7 +1958,6 @@
},
"node_modules/@bergos/jsonparse/node_modules/buffer": {
"version": "6.0.3",
"dev": true,
"funding": [
{
"type": "github",
@ -5872,6 +5870,17 @@
"node": ">=8"
}
},
"node_modules/@janeirodigital/interop-utils": {
"version": "1.0.0-rc.24",
"resolved": "https://registry.npmjs.org/@janeirodigital/interop-utils/-/interop-utils-1.0.0-rc.24.tgz",
"integrity": "sha512-mLOhitq6SyRSZi1DxrzTTgms7Mt0zgx/5KezkkyMBH3OYuYJBGPH6A93iBJl0wA5Ln90A9KnyiC7I/7+IUYhoQ==",
"license": "MIT",
"dependencies": {
"http-link-header": "^1.1.1",
"jsonld-streaming-parser": "^3.2.1",
"n3": "^1.17.1"
}
},
"node_modules/@jest/console": {
"version": "27.5.1",
"license": "MIT",
@ -7539,6 +7548,27 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@solid-notifications/discovery": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@solid-notifications/discovery/-/discovery-0.1.2.tgz",
"integrity": "sha512-jkqV+Ceknw2XE0Vl/4O2BBFnkCZQhNDVt6B9nzbVD4T3aNhMlK/gZS6oNHqa23obgFNCtgFBmeeRKiN1/v8lcw==",
"license": "MIT",
"dependencies": {
"@janeirodigital/interop-utils": "^1.0.0-rc.24",
"n3": "^1.17.2"
}
},
"node_modules/@solid-notifications/subscription": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@solid-notifications/subscription/-/subscription-0.1.2.tgz",
"integrity": "sha512-XnnqNsLOIdUAzB11aROzfRiJLHJjTOaHMSrnn3teQRtE0BwpbnAJtzGG/m3JNUR+QqyjKkB3jfibxJjzvI/HQg==",
"license": "MIT",
"dependencies": {
"@janeirodigital/interop-utils": "^1.0.0-rc.24",
"@solid-notifications/discovery": "^0.1.2",
"n3": "^1.17.2"
}
},
"node_modules/@solid/access-control-policy": {
"version": "0.1.3",
"dev": true,
@ -8693,7 +8723,6 @@
},
"node_modules/@types/readable-stream": {
"version": "2.3.15",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
@ -8702,7 +8731,6 @@
},
"node_modules/@types/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"dev": true,
"license": "MIT"
},
"node_modules/@types/resolve": {
@ -18004,7 +18032,6 @@
},
"node_modules/jsonld-streaming-parser": {
"version": "3.3.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@bergos/jsonparse": "^1.4.0",
@ -18021,7 +18048,6 @@
},
"node_modules/jsonld-streaming-parser/node_modules/buffer": {
"version": "6.0.3",
"dev": true,
"funding": [
{
"type": "github",
@ -18044,12 +18070,10 @@
},
"node_modules/jsonld-streaming-parser/node_modules/canonicalize": {
"version": "1.0.8",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/jsonld-streaming-parser/node_modules/readable-stream": {
"version": "4.5.2",
"dev": true,
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
@ -28572,7 +28596,9 @@
}
},
"node_modules/ws": {
"version": "8.16.0",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@ -29592,8 +29618,10 @@
"@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"@solid-notifications/subscription": "^0.1.2",
"cross-fetch": "^3.1.6",
"http-link-header": "^1.1.1"
"http-link-header": "^1.1.1",
"ws": "^8.18.0"
},
"devDependencies": {
"@inrupt/solid-client-authn-core": "^2.2.6",

@ -44,8 +44,10 @@
"@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"@solid-notifications/subscription": "^0.1.2",
"cross-fetch": "^3.1.6",
"http-link-header": "^1.1.1"
"http-link-header": "^1.1.1",
"ws": "^8.18.0"
},
"files": [
"dist",

@ -99,9 +99,13 @@ export async function readResource(
try {
const fetch = guaranteeFetch(options?.fetch);
// Fetch options to determine the document type
console.log("Will make fetch");
console.log(uri);
const response = await fetch(uri, {
headers: { accept: "text/turtle, */*" },
});
console.log("Lets just confirm its this fetch");
console.log(response);
if (response.status === 404) {
return {
isError: false,
@ -128,7 +132,9 @@ export async function readResource(
if (contentType.startsWith("text/turtle")) {
// Parse Turtle
console.log("Before text");
const rawTurtle = await response.text();
console.log("After Text");
if (options?.dataset) {
const result = await addRawTurtleToDataset(
rawTurtle,
@ -166,6 +172,7 @@ export async function readResource(
};
}
} catch (err) {
console.log("We're in this error", err);
return UnexpectedResourceError.fromThrown(uri, err);
}
}

@ -33,6 +33,13 @@ import { NoncompliantPodError } from "../requester/results/error/NoncompliantPod
import { setWacRuleForAclUri, type SetWacRuleResult } from "./wac/setWacRule";
import type { LeafUri } from "../util/uriTypes";
import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError";
import type {
CloseSubscriptionResult,
NotificationSubscription,
OpenSubscriptionResult,
} from "./notifications/NotificationSubscription";
import { Websocket2023NotificationSubscription } from "./notifications/Websocket2023NotificationSubscription";
import type { NotificationMessage } from "./notifications/NotificationMessage";
/**
* Statuses shared between both Leaf and Container
@ -44,6 +51,7 @@ export type SharedStatuses = Unfetched | DeleteResult | CreateSuccess;
*/
export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
update: () => void;
notification: () => void;
}>) {
/**
* @internal
@ -96,6 +104,12 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
*/
protected wacRule?: WacRule;
/**
* @internal
* Handles notification subscriptions
*/
protected notificationSubscription?: NotificationSubscription;
/**
* @param context - SolidLdoDatasetContext for the parent dataset
*/
@ -271,7 +285,7 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
* ```typescript
* // Logs "undefined"
* console.log(resource.isAbsent());
* const result = resource.read();
* const result = await resource.read();
* if (!result.isError) {
* // False if the resource exists, true if it does not
* console.log(resource.isAbsent());
@ -290,7 +304,7 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
* ```typescript
* // Logs "undefined"
* console.log(resource.isPresent());
* const result = resource.read();
* const result = await resource.read();
* if (!result.isError) {
* // True if the resource exists, false if it does not
* console.log(resource.isPresent());
@ -301,6 +315,26 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
return this.absent === undefined ? undefined : !this.absent;
}
/**
* Is this resource currently listening to notifications from this document
* @returns true if the resource is subscribed to notifications, false if not
*
* @example
* ```typescript
* // Logs "undefined"
* console.log(resource.isPresent());
* const result = resource.read();
* if (!result.isError) {
* // True if the resource exists, false if it does not
* console.log(resource.isPresent());
* }
* ```
*/
isSubscribedToNotifications(): boolean {
// TODO
throw new Error("Not Implemented");
}
/**
* ===========================================================================
* HELPER METHODS
@ -685,4 +719,49 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
this.wacRule = result.wacRule;
return result;
}
/**
* ===========================================================================
* SUBSCRIPTION METHODS
* ===========================================================================
*/
/**
* TODO
*/
async subscribeToNotifications(): Promise<OpenSubscriptionResult> {
this.notificationSubscription = new Websocket2023NotificationSubscription(
this,
this.onNotification.bind(this),
this.context,
);
return await this.notificationSubscription.open();
}
/**
* @internal
* TODO
*/
protected async onNotification(message: NotificationMessage): Promise<void> {
switch (message.type) {
case "Update":
const readResult = await this.read();
console.log(readResult);
}
}
/**
* TODO
*/
async unsubscribeFromNotifications(): Promise<CloseSubscriptionResult> {
const result = await this.notificationSubscription?.close();
this.notificationSubscription = undefined;
return (
result ?? {
type: "unsubscribeFromNotificationSuccess",
isError: false,
uri: this.uri,
}
);
}
}

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

@ -0,0 +1,35 @@
import type { UnexpectedResourceError } from "../../requester/results/error/ErrorResult";
import type { SolidLdoDatasetContext } from "../../SolidLdoDatasetContext";
import type { Resource } from "../Resource";
import type { NotificationMessage } from "./NotificationMessage";
import type { UnsupportedNotificationError } from "./results/NotificationErrors";
import type { SubscribeToNotificationSuccess } from "./results/SubscribeToNotificationSuccess";
import type { UnsubscribeToNotificationSuccess } from "./results/UnsubscribeFromNotificationSuccess";
export type OpenSubscriptionResult =
| SubscribeToNotificationSuccess
| UnsupportedNotificationError
| UnexpectedResourceError;
export type CloseSubscriptionResult =
| UnsubscribeToNotificationSuccess
| UnexpectedResourceError;
export abstract class NotificationSubscription {
protected resource: Resource;
protected onNotification: (message: NotificationMessage) => void;
protected context: SolidLdoDatasetContext;
constructor(
resource: Resource,
onNotification: (message: NotificationMessage) => void,
context: SolidLdoDatasetContext,
) {
this.resource = resource;
this.onNotification = onNotification;
this.context = context;
}
abstract open(): Promise<OpenSubscriptionResult>;
abstract close(): Promise<CloseSubscriptionResult>;
}

@ -0,0 +1,61 @@
import { UnexpectedResourceError } from "../../requester/results/error/ErrorResult";
import type {
CloseSubscriptionResult,
OpenSubscriptionResult,
} from "./NotificationSubscription";
import { NotificationSubscription } from "./NotificationSubscription";
import { SubscriptionClient } from "@solid-notifications/subscription";
import { WebSocket } from "ws";
import { UnsupportedNotificationError } from "./results/NotificationErrors";
import type { NotificationMessage } from "./NotificationMessage";
const CHANNEL_TYPE =
"http://www.w3.org/ns/solid/notifications#WebSocketChannel2023";
export class Websocket2023NotificationSubscription extends NotificationSubscription {
private socket: WebSocket | undefined;
async open(): Promise<OpenSubscriptionResult> {
const client = new SubscriptionClient(this.context.fetch);
try {
const notificationChannel = await client.subscribe(
this.resource.uri,
CHANNEL_TYPE,
);
return new Promise<OpenSubscriptionResult>((resolve) => {
this.socket = new WebSocket(notificationChannel.receiveFrom);
this.socket.onmessage = (message) => {
const messageData = message.data.toString();
// TODO uncompliant Pod error on misformatted message
this.onNotification(JSON.parse(messageData) as NotificationMessage);
};
this.socket.onerror = (err) =>
resolve(UnexpectedResourceError.fromThrown(this.resource.uri, err));
this.socket.onopen = () => {
resolve({
isError: false,
type: "subscribeToNotificationSuccess",
uri: this.resource.uri,
});
};
});
} catch (err) {
if (
err instanceof Error &&
err.message.startsWith("Discovery did not succeed")
) {
return new UnsupportedNotificationError(this.resource.uri, err.message);
}
return UnexpectedResourceError.fromThrown(this.resource.uri, err);
}
}
async close(): Promise<CloseSubscriptionResult> {
this.socket?.terminate();
return {
type: "unsubscribeFromNotificationSuccess",
isError: false,
uri: this.resource.uri,
};
}
}

@ -0,0 +1,5 @@
import { ResourceError } from "../../../requester/results/error/ErrorResult";
export class UnsupportedNotificationError extends ResourceError {
readonly type = "unexpectedResourceError" as const;
}

@ -0,0 +1,8 @@
import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult";
/**
* Returned when a notification has been successfully subscribed to for a resource
*/
export interface SubscribeToNotificationSuccess extends ResourceSuccess {
type: "subscribeToNotificationSuccess";
}

@ -0,0 +1,8 @@
import type { ResourceSuccess } from "../../../requester/results/success/SuccessResult";
/**
* Returned when a notification has been successfully unsubscribed from for a resource
*/
export interface UnsubscribeToNotificationSuccess extends ResourceSuccess {
type: "unsubscribeFromNotificationSuccess";
}

@ -42,6 +42,7 @@ import type { GetWacRuleSuccess } from "../src/resource/wac/results/GetWacRuleSu
import type { WacRule } from "../src/resource/wac/WacRule";
import type { GetStorageContainerFromWebIdSuccess } from "../src/requester/results/success/CheckRootContainerSuccess";
import { generateAuthFetch } from "./authFetch.helper";
import { wait } from "./utils.helper";
const TEST_CONTAINER_SLUG = "test_ldo/";
const TEST_CONTAINER_URI =
@ -177,14 +178,16 @@ describe("Integration", () => {
beforeEach(async () => {
fetchMock = jest.fn(authFetch);
solidLdoDataset = createSolidLdoDataset({ fetch: fetchMock });
console.log(ROOT_CONTAINER, TEST_CONTAINER_SLUG);
// Create a new document called sample.ttl
await authFetch(ROOT_CONTAINER, {
const result = await authFetch(ROOT_CONTAINER, {
method: "POST",
headers: {
link: '<http://www.w3.org/ns/ldp#Container>; rel="type"',
slug: TEST_CONTAINER_SLUG,
},
});
console.log("Create Result", result);
await authFetch(TEST_CONTAINER_ACL_URI, {
method: "PUT",
headers: {
@ -2010,4 +2013,71 @@ describe("Integration", () => {
expect(wacResult.type).toBe("serverError");
});
});
/**
* ===========================================================================
* NOTIFICATION SUBSCRIPTIONS
* ===========================================================================
*/
describe("Notification Subscriptions", () => {
it("Notification is propogated when a resource is updated", async () => {
expect(true).toBe(true);
const spidermanNode = namedNode("http://example.org/#spiderman");
const foafNameNode = namedNode("http://xmlns.com/foaf/0.1/name");
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
await resource.read();
const spidermanCallback = jest.fn();
solidLdoDataset.addListener([spidermanNode, null, null, null], jest.fn());
// const subscriptionResult = await resource.subscribeToNotifications();
// expect(subscriptionResult.type).toBe("subscribeToNotificationSuccess");
await authFetch(SAMPLE_DATA_URI, {
method: "PATCH",
body: 'INSERT DATA { <http://example.org/#spiderman> <http://xmlns.com/foaf/0.1/name> "Peter Parker" . }',
headers: {
"Content-Type": "application/sparql-update",
},
});
await wait(1000);
process.env.AFTER = "TRUE";
await resource.read();
expect(spidermanCallback).toHaveBeenCalledTimes(1);
expect(
solidLdoDataset.match(
spidermanNode,
foafNameNode,
literal("Peter Parker"),
).size,
).toBe(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);
});
});
});

Loading…
Cancel
Save