Full code coverage on read

main
jaxoncreed 2 years ago
parent e4af4f641a
commit 0c0f037786
  1. 2
      packages/solid/src/requester/results/error/HttpErrorResult.ts
  2. 7
      packages/solid/src/util/RequestBatcher.ts
  3. 2
      packages/solid/src/util/rdfUtils.ts
  4. 158
      packages/solid/test/ContainerRequester.test.ts
  5. 472
      packages/solid/test/LeafRequester.test.ts
  6. 39
      packages/solid/test/RequestBatcher.test.ts
  7. 217
      packages/solid/test/SolidLdoDataset.test.ts
  8. 7
      packages/solid/test/configs/solid-css-seed.json
  9. 27
      packages/solid/test/solidServer.helper.ts

@ -30,7 +30,7 @@ export abstract class HttpErrorResult extends ResourceError {
static isnt(response: Response) { static isnt(response: Response) {
return ( return (
!(response.status >= 200 || response.status < 300) && !(response.status >= 200 && response.status < 300) &&
response.status !== 404 && response.status !== 404 &&
response.status !== 304 response.status !== 304
); );

@ -35,16 +35,13 @@ export class RequestBatcher {
private currentlyProcessing: WaitingProcess<any[], any> | undefined = private currentlyProcessing: WaitingProcess<any[], any> | undefined =
undefined; undefined;
private processQueue: WaitingProcess<any[], any>[] = []; private processQueue: WaitingProcess<any[], any>[] = [];
public shouldBatchAllRequests: boolean;
public batchMillis: number; public batchMillis: number;
constructor( constructor(
options?: Partial<{ options?: Partial<{
shouldBatchAllRequests: boolean;
batchMillis: number; batchMillis: number;
}>, }>,
) { ) {
this.shouldBatchAllRequests = options?.shouldBatchAllRequests || false;
this.batchMillis = options?.batchMillis || 1000; this.batchMillis = options?.batchMillis || 1000;
} }
@ -56,9 +53,7 @@ export class RequestBatcher {
if (!this.processQueue[0]) { if (!this.processQueue[0]) {
return; return;
} }
const processName = this.shouldBatchAllRequests const processName = this.processQueue[0].name;
? ANY_KEY
: this.processQueue[0].name;
// Set last request timestamp if not available // Set last request timestamp if not available
if (!this.lastRequestTimestampMap[processName]) { if (!this.lastRequestTimestampMap[processName]) {

@ -88,7 +88,7 @@ export async function addRawTurtleToDataset(
const error = UnexpectedResourceError.fromThrown(baseUri, err); const error = UnexpectedResourceError.fromThrown(baseUri, err);
return new NoncompliantPodError( return new NoncompliantPodError(
baseUri, baseUri,
`Request at ${baseUri} returned noncompliant turtle: ${error.message}`, `Request returned noncompliant turtle: ${error.message}`,
); );
} }

@ -1,88 +1,88 @@
import type { App } from "@solid/community-server"; // import type { App } from "@solid/community-server";
import { getAuthenticatedFetch, ROOT_COONTAINER } from "./solidServer.helper"; // import { getAuthenticatedFetch, ROOT_CONTAINER } from "./solidServer.helper";
import type { SolidLdoDataset } from "../src/SolidLdoDataset"; // import type { SolidLdoDataset } from "../src/SolidLdoDataset";
import { createSolidLdoDataset } from "../src/createSolidLdoDataset"; // import { createSolidLdoDataset } from "../src/createSolidLdoDataset";
import { ContainerRequester } from "../src/requester/ContainerRequester"; // import { ContainerRequester } from "../src/requester/ContainerRequester";
import type { ContainerUri } from "../src/util/uriTypes"; // import type { ContainerUri } from "../src/util/uriTypes";
describe.skip("Container Requester", () => { // describe.skip("Container Requester", () => {
let _app: App; // let _app: App;
let authFetch: typeof fetch; // let authFetch: typeof fetch;
let fetchMock: typeof fetch; // let fetchMock: typeof fetch;
let solidLdoDataset: SolidLdoDataset; // let solidLdoDataset: SolidLdoDataset;
beforeAll(async () => { // beforeAll(async () => {
// Start up the server // // Start up the server
// app = await createApp(); // // app = await createApp();
// await app.start(); // // await app.start();
authFetch = await getAuthenticatedFetch(); // authFetch = await getAuthenticatedFetch();
}); // });
beforeEach(async () => { // beforeEach(async () => {
fetchMock = jest.fn(authFetch); // fetchMock = jest.fn(authFetch);
solidLdoDataset = createSolidLdoDataset({ fetch: fetchMock }); // solidLdoDataset = createSolidLdoDataset({ fetch: fetchMock });
// Create a new document called sample.ttl // // Create a new document called sample.ttl
await Promise.all([ // await Promise.all([
authFetch(`${ROOT_COONTAINER}test_leaf/`, { // authFetch(`${ROOT_CONTAINER}test_leaf/`, {
method: "POST", // method: "POST",
headers: { "content-type": "text/turtle", slug: "sample.ttl" }, // headers: { "content-type": "text/turtle", slug: "sample.ttl" },
body: `@base <http://example.org/> . // body: `@base <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . // @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . // @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> . // @prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rel: <http://www.perceive.net/schemas/relationship/> . // @prefix rel: <http://www.perceive.net/schemas/relationship/> .
<#green-goblin> // <#green-goblin>
rel:enemyOf <#spiderman> ; // rel:enemyOf <#spiderman> ;
a foaf:Person ; # in the context of the Marvel universe // a foaf:Person ; # in the context of the Marvel universe
foaf:name "Green Goblin" . // foaf:name "Green Goblin" .
<#spiderman> // <#spiderman>
rel:enemyOf <#green-goblin> ; // rel:enemyOf <#green-goblin> ;
a foaf:Person ; // a foaf:Person ;
foaf:name "Spiderman", "Человек-паук"@ru .`, // foaf:name "Spiderman", "Человек-паук"@ru .`,
}), // }),
authFetch(`${ROOT_COONTAINER}test_leaf/`, { // authFetch(`${ROOT_COONTAINER}test_leaf/`, {
method: "PUT", // method: "PUT",
headers: { "content-type": "text/plain", slug: "sample.txt" }, // headers: { "content-type": "text/plain", slug: "sample.txt" },
body: `some text.`, // body: `some text.`,
}), // }),
]); // ]);
}); // });
afterEach(async () => { // afterEach(async () => {
await Promise.all([ // await Promise.all([
authFetch(`${ROOT_COONTAINER}test_leaf/sample.ttl`, { // authFetch(`${ROOT_COONTAINER}test_leaf/sample.ttl`, {
method: "DELETE", // method: "DELETE",
}), // }),
authFetch(`${ROOT_COONTAINER}test_leaf/sample2.ttl`, { // authFetch(`${ROOT_COONTAINER}test_leaf/sample2.ttl`, {
method: "DELETE", // method: "DELETE",
}), // }),
authFetch(`${ROOT_COONTAINER}test_leaf/sample.txt`, { // authFetch(`${ROOT_COONTAINER}test_leaf/sample.txt`, {
method: "DELETE", // method: "DELETE",
}), // }),
authFetch(`${ROOT_COONTAINER}test_leaf/sample2.txt`, { // authFetch(`${ROOT_COONTAINER}test_leaf/sample2.txt`, {
method: "DELETE", // method: "DELETE",
}), // }),
]); // ]);
}); // });
it("Checks if a root container is a root container", async () => { // it("Checks if a root container is a root container", async () => {
const leafRequester = new ContainerRequester( // const leafRequester = new ContainerRequester(
`${ROOT_COONTAINER}` as ContainerUri, // `${ROOT_COONTAINER}` as ContainerUri,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.isRootContainer(); // const result = await leafRequester.isRootContainer();
expect(result).toBe(true); // expect(result).toBe(true);
}); // });
it("Checks if a non root container is a root container", async () => { // it("Checks if a non root container is a root container", async () => {
const leafRequester = new ContainerRequester( // const leafRequester = new ContainerRequester(
`${ROOT_COONTAINER}/test_leaf/`, // `${ROOT_COONTAINER}/test_leaf/`,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.isRootContainer(); // const result = await leafRequester.isRootContainer();
expect(result).toBe(false); // expect(result).toBe(false);
}); // });
}); // });

@ -1,250 +1,250 @@
import type { App } from "@solid/community-server"; // import type { App } from "@solid/community-server";
import { getAuthenticatedFetch, ROOT_COONTAINER } from "./solidServer.helper"; // import { getAuthenticatedFetch, ROOT_COONTAINER } from "./solidServer.helper";
import type { SolidLdoDataset } from "../src/SolidLdoDataset"; // import type { SolidLdoDataset } from "../src/SolidLdoDataset";
import { createSolidLdoDataset } from "../src/createSolidLdoDataset"; // import { createSolidLdoDataset } from "../src/createSolidLdoDataset";
import { LeafRequester } from "../src/requester/LeafRequester"; // import { LeafRequester } from "../src/requester/LeafRequester";
import { namedNode, quad as createQuad } from "@rdfjs/data-model"; // import { namedNode, quad as createQuad } from "@rdfjs/data-model";
describe.skip("Leaf Requester", () => { // describe.skip("Leaf Requester", () => {
let _app: App; // let _app: App;
let authFetch: typeof fetch; // let authFetch: typeof fetch;
let fetchMock: typeof fetch; // let fetchMock: typeof fetch;
let solidLdoDataset: SolidLdoDataset; // let solidLdoDataset: SolidLdoDataset;
beforeAll(async () => { // beforeAll(async () => {
// Start up the server // // Start up the server
// app = await createApp(); // // app = await createApp();
// await app.start(); // // await app.start();
authFetch = await getAuthenticatedFetch(); // authFetch = await getAuthenticatedFetch();
}); // });
beforeEach(async () => { // beforeEach(async () => {
fetchMock = jest.fn(authFetch); // fetchMock = jest.fn(authFetch);
solidLdoDataset = createSolidLdoDataset({ fetch: fetchMock }); // solidLdoDataset = createSolidLdoDataset({ fetch: fetchMock });
// Create a new document called sample.ttl // // Create a new document called sample.ttl
await Promise.all([ // await Promise.all([
authFetch(`${ROOT_COONTAINER}test_leaf/`, { // authFetch(`${ROOT_COONTAINER}test_leaf/`, {
method: "POST", // method: "POST",
headers: { "content-type": "text/turtle", slug: "sample.ttl" }, // headers: { "content-type": "text/turtle", slug: "sample.ttl" },
body: `@base <http://example.org/> . // body: `@base <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . // @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . // @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> . // @prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rel: <http://www.perceive.net/schemas/relationship/> . // @prefix rel: <http://www.perceive.net/schemas/relationship/> .
<#green-goblin> // <#green-goblin>
rel:enemyOf <#spiderman> ; // rel:enemyOf <#spiderman> ;
a foaf:Person ; # in the context of the Marvel universe // a foaf:Person ; # in the context of the Marvel universe
foaf:name "Green Goblin" . // foaf:name "Green Goblin" .
<#spiderman> // <#spiderman>
rel:enemyOf <#green-goblin> ; // rel:enemyOf <#green-goblin> ;
a foaf:Person ; // a foaf:Person ;
foaf:name "Spiderman", "Человек-паук"@ru .`, // foaf:name "Spiderman", "Человек-паук"@ru .`,
}), // }),
authFetch(`${ROOT_COONTAINER}test_leaf/`, { // authFetch(`${ROOT_COONTAINER}test_leaf/`, {
method: "PUT", // method: "PUT",
headers: { "content-type": "text/plain", slug: "sample.txt" }, // headers: { "content-type": "text/plain", slug: "sample.txt" },
body: `some text.`, // body: `some text.`,
}), // }),
]); // ]);
}); // });
afterEach(async () => { // afterEach(async () => {
await Promise.all([ // await Promise.all([
authFetch(`${ROOT_COONTAINER}test_leaf/sample.ttl`, { // authFetch(`${ROOT_COONTAINER}test_leaf/sample.ttl`, {
method: "DELETE", // method: "DELETE",
}), // }),
authFetch(`${ROOT_COONTAINER}test_leaf/sample2.ttl`, { // authFetch(`${ROOT_COONTAINER}test_leaf/sample2.ttl`, {
method: "DELETE", // method: "DELETE",
}), // }),
authFetch(`${ROOT_COONTAINER}test_leaf/sample.txt`, { // authFetch(`${ROOT_COONTAINER}test_leaf/sample.txt`, {
method: "DELETE", // method: "DELETE",
}), // }),
authFetch(`${ROOT_COONTAINER}test_leaf/sample2.txt`, { // authFetch(`${ROOT_COONTAINER}test_leaf/sample2.txt`, {
method: "DELETE", // method: "DELETE",
}), // }),
]); // ]);
}); // });
/** // /**
* =========================================================================== // * ===========================================================================
* Read // * Read
* =========================================================================== // * ===========================================================================
*/ // */
it("reads data", async () => { // it("reads data", async () => {
const leafRequester = new LeafRequester( // const leafRequester = new LeafRequester(
`${ROOT_COONTAINER}test_leaf/sample.ttl`, // `${ROOT_COONTAINER}test_leaf/sample.ttl`,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.read(); // const result = await leafRequester.read();
expect(result.type).toBe("data"); // expect(result.type).toBe("data");
expect( // expect(
solidLdoDataset.match( // solidLdoDataset.match(
null, // null,
null, // null,
null, // null,
namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`),
).size, // ).size,
).toBe(7); // ).toBe(7);
}); // });
it("reads data that doesn't exist", async () => { // it("reads data that doesn't exist", async () => {
const leafRequester = new LeafRequester( // const leafRequester = new LeafRequester(
`${ROOT_COONTAINER}test_leaf/doesnotexist.ttl`, // `${ROOT_COONTAINER}test_leaf/doesnotexist.ttl`,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.read(); // const result = await leafRequester.read();
expect(result.type).toBe("absent"); // expect(result.type).toBe("absent");
}); // });
/** // /**
* =========================================================================== // * ===========================================================================
* Create // * Create
* =========================================================================== // * ===========================================================================
*/ // */
it("creates a data resource that doesn't exist while not overwriting", async () => { // it("creates a data resource that doesn't exist while not overwriting", async () => {
const leafRequester = new LeafRequester( // const leafRequester = new LeafRequester(
`${ROOT_COONTAINER}test_leaf/sample2.ttl`, // `${ROOT_COONTAINER}test_leaf/sample2.ttl`,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.createDataResource(); // const result = await leafRequester.createDataResource();
expect(result.type).toBe("data"); // expect(result.type).toBe("data");
expect( // expect(
solidLdoDataset.has( // solidLdoDataset.has(
createQuad( // createQuad(
namedNode(`${ROOT_COONTAINER}test_leaf/`), // namedNode(`${ROOT_COONTAINER}test_leaf/`),
namedNode("http://www.w3.org/ns/ldp#contains"), // namedNode("http://www.w3.org/ns/ldp#contains"),
namedNode(`${ROOT_COONTAINER}test_leaf/sample2.ttl`), // namedNode(`${ROOT_COONTAINER}test_leaf/sample2.ttl`),
namedNode(`${ROOT_COONTAINER}test_leaf/`), // namedNode(`${ROOT_COONTAINER}test_leaf/`),
), // ),
), // ),
).toBe(true); // ).toBe(true);
}); // });
it("creates a data resource that doesn't exist while overwriting", async () => { // it("creates a data resource that doesn't exist while overwriting", async () => {
const leafRequester = new LeafRequester( // const leafRequester = new LeafRequester(
`${ROOT_COONTAINER}test_leaf/sample2.ttl`, // `${ROOT_COONTAINER}test_leaf/sample2.ttl`,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.createDataResource(true); // const result = await leafRequester.createDataResource(true);
expect(result.type).toBe("data"); // expect(result.type).toBe("data");
expect( // expect(
solidLdoDataset.has( // solidLdoDataset.has(
createQuad( // createQuad(
namedNode(`${ROOT_COONTAINER}test_leaf/`), // namedNode(`${ROOT_COONTAINER}test_leaf/`),
namedNode("http://www.w3.org/ns/ldp#contains"), // namedNode("http://www.w3.org/ns/ldp#contains"),
namedNode(`${ROOT_COONTAINER}test_leaf/sample2.ttl`), // namedNode(`${ROOT_COONTAINER}test_leaf/sample2.ttl`),
namedNode(`${ROOT_COONTAINER}test_leaf/`), // namedNode(`${ROOT_COONTAINER}test_leaf/`),
), // ),
), // ),
).toBe(true); // ).toBe(true);
}); // });
it("creates a data resource that does exist while not overwriting", async () => { // it("creates a data resource that does exist while not overwriting", async () => {
const leafRequester = new LeafRequester( // const leafRequester = new LeafRequester(
`${ROOT_COONTAINER}test_leaf/sample.ttl`, // `${ROOT_COONTAINER}test_leaf/sample.ttl`,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.createDataResource(); // const result = await leafRequester.createDataResource();
expect(result.type).toBe("data"); // expect(result.type).toBe("data");
expect( // expect(
solidLdoDataset.has( // solidLdoDataset.has(
createQuad( // createQuad(
namedNode("http://example.org/#spiderman"), // namedNode("http://example.org/#spiderman"),
namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), // namedNode("http://www.perceive.net/schemas/relationship/enemyOf"),
namedNode("http://example.org/#green-goblin"), // namedNode("http://example.org/#green-goblin"),
namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`),
), // ),
), // ),
).toBe(true); // ).toBe(true);
expect( // expect(
solidLdoDataset.has( // solidLdoDataset.has(
createQuad( // createQuad(
namedNode(`${ROOT_COONTAINER}test_leaf/`), // namedNode(`${ROOT_COONTAINER}test_leaf/`),
namedNode("http://www.w3.org/ns/ldp#contains"), // namedNode("http://www.w3.org/ns/ldp#contains"),
namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`),
namedNode(`${ROOT_COONTAINER}test_leaf/`), // namedNode(`${ROOT_COONTAINER}test_leaf/`),
), // ),
), // ),
).toBe(true); // ).toBe(true);
}); // });
it("creates a data resource that does exist while overwriting", async () => { // it("creates a data resource that does exist while overwriting", async () => {
const leafRequester = new LeafRequester( // const leafRequester = new LeafRequester(
`${ROOT_COONTAINER}test_leaf/sample.ttl`, // `${ROOT_COONTAINER}test_leaf/sample.ttl`,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.createDataResource(true); // const result = await leafRequester.createDataResource(true);
expect(result.type).toBe("data"); // expect(result.type).toBe("data");
expect( // expect(
solidLdoDataset.has( // solidLdoDataset.has(
createQuad( // createQuad(
namedNode("http://example.org/#spiderman"), // namedNode("http://example.org/#spiderman"),
namedNode("http://www.perceive.net/schemas/relationship/enemyOf"), // namedNode("http://www.perceive.net/schemas/relationship/enemyOf"),
namedNode("http://example.org/#green-goblin"), // namedNode("http://example.org/#green-goblin"),
namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`),
), // ),
), // ),
).toBe(false); // ).toBe(false);
expect( // expect(
solidLdoDataset.has( // solidLdoDataset.has(
createQuad( // createQuad(
namedNode(`${ROOT_COONTAINER}test_leaf/`), // namedNode(`${ROOT_COONTAINER}test_leaf/`),
namedNode("http://www.w3.org/ns/ldp#contains"), // namedNode("http://www.w3.org/ns/ldp#contains"),
namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}test_leaf/sample.ttl`),
namedNode(`${ROOT_COONTAINER}test_leaf/`), // namedNode(`${ROOT_COONTAINER}test_leaf/`),
), // ),
), // ),
).toBe(true); // ).toBe(true);
}); // });
/** // /**
* =========================================================================== // * ===========================================================================
* Delete // * Delete
* =========================================================================== // * ===========================================================================
*/ // */
it("deletes data", async () => { // it("deletes data", async () => {
solidLdoDataset.add( // solidLdoDataset.add(
createQuad( // createQuad(
namedNode("a"), // namedNode("a"),
namedNode("b"), // namedNode("b"),
namedNode("c"), // namedNode("c"),
namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`),
), // ),
); // );
solidLdoDataset.add( // solidLdoDataset.add(
createQuad( // createQuad(
namedNode(`${ROOT_COONTAINER}/test_leaf/`), // namedNode(`${ROOT_COONTAINER}/test_leaf/`),
namedNode("http://www.w3.org/ns/ldp#contains"), // namedNode("http://www.w3.org/ns/ldp#contains"),
namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`),
namedNode(`${ROOT_COONTAINER}/test_leaf/`), // namedNode(`${ROOT_COONTAINER}/test_leaf/`),
), // ),
); // );
const leafRequester = new LeafRequester( // const leafRequester = new LeafRequester(
`${ROOT_COONTAINER}/test_leaf/sample.ttl`, // `${ROOT_COONTAINER}/test_leaf/sample.ttl`,
solidLdoDataset.context, // solidLdoDataset.context,
); // );
const result = await leafRequester.delete(); // const result = await leafRequester.delete();
expect(result.type).toBe("absent"); // expect(result.type).toBe("absent");
expect( // expect(
solidLdoDataset.match( // solidLdoDataset.match(
null, // null,
null, // null,
null, // null,
namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`),
).size, // ).size,
).toBe(0); // ).toBe(0);
expect( // expect(
solidLdoDataset.has( // solidLdoDataset.has(
createQuad( // createQuad(
namedNode(`${ROOT_COONTAINER}/test_leaf/`), // namedNode(`${ROOT_COONTAINER}/test_leaf/`),
namedNode("http://www.w3.org/ns/ldp#contains"), // namedNode("http://www.w3.org/ns/ldp#contains"),
namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`), // namedNode(`${ROOT_COONTAINER}/test_leaf/sample.ttl`),
namedNode(`${ROOT_COONTAINER}/test_leaf/`), // namedNode(`${ROOT_COONTAINER}/test_leaf/`),
), // ),
), // ),
).toBe(false); // ).toBe(false);
}); // });
}); // });

@ -5,11 +5,17 @@ describe("RequestBatcher", () => {
type ReadWaitingProcess = WaitingProcess<[string], string>; type ReadWaitingProcess = WaitingProcess<[string], string>;
it("Batches a request", async () => { it("Batches a request", async () => {
const requestBatcher = new RequestBatcher({ batchMillis: 1000 }); const requestBatcher = new RequestBatcher({ batchMillis: 500 });
const perform = async (input: string): Promise<string> => `Hello ${input}`; const perform = async (input: string): Promise<string> => {
await wait(100);
return `Hello ${input}`;
};
const perform1 = jest.fn(perform); const perform1 = jest.fn(perform);
const perform2 = jest.fn(perform); const perform2 = jest.fn(perform);
const perform3 = jest.fn(perform); const perform3 = jest.fn((input: string): Promise<string> => {
expect(requestBatcher.isLoading("read")).toBe(true);
return perform(input);
});
const perform4 = jest.fn(perform); const perform4 = jest.fn(perform);
const modifyQueue = (queue, currentlyProcessing, input: [string]) => { const modifyQueue = (queue, currentlyProcessing, input: [string]) => {
@ -26,6 +32,8 @@ describe("RequestBatcher", () => {
let return3: string = ""; let return3: string = "";
let return4: string = ""; let return4: string = "";
expect(requestBatcher.isLoading("read")).toBe(false);
await Promise.all([ await Promise.all([
requestBatcher requestBatcher
.queueProcess<[string], string>({ .queueProcess<[string], string>({
@ -76,4 +84,29 @@ describe("RequestBatcher", () => {
expect(perform3).toHaveBeenCalledTimes(0); expect(perform3).toHaveBeenCalledTimes(0);
expect(perform4).toHaveBeenCalledTimes(0); expect(perform4).toHaveBeenCalledTimes(0);
}); });
it("sets a default batch millis", () => {
const requestBatcher = new RequestBatcher();
expect(requestBatcher.batchMillis).toBe(1000);
});
it("handles an error being thrown in the process", () => {
const requestBatcher = new RequestBatcher({ batchMillis: 500 });
const perform = async (_input: string): Promise<string> => {
throw new Error("Test Error");
};
const perform1 = jest.fn(perform);
expect(() =>
requestBatcher.queueProcess<[string], string>({
name: "read",
args: ["a"],
perform: perform1,
modifyQueue: () => undefined,
}),
).rejects.toThrowError("Test Error");
});
}); });
function wait(millis: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, millis));
}

@ -0,0 +1,217 @@
import type { App } from "@solid/community-server";
import type { ContainerUri, LeafUri, SolidLdoDataset } from "../src";
import { createSolidLdoDataset } from "../src";
import {
ROOT_CONTAINER,
createApp,
getAuthenticatedFetch,
} from "./solidServer.helper";
import { namedNode } from "@rdfjs/data-model";
const TEST_CONTAINER_SLUG = "test_ldo/";
const TEST_CONTAINER_URI =
`${ROOT_CONTAINER}${TEST_CONTAINER_SLUG}` as ContainerUri;
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 SPIDER_MAN_TTL = `@base <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rel: <http://www.perceive.net/schemas/relationship/> .
<#green-goblin>
rel:enemyOf <#spiderman> ;
a foaf:Person ; # in the context of the Marvel universe
foaf:name "Green Goblin" .
<#spiderman>
rel:enemyOf <#green-goblin> ;
a foaf:Person ;
foaf:name "Spiderman", "Человек-паук"@ru .`;
describe("SolidLdoDataset", () => {
let app: App;
let authFetch: typeof fetch;
let fetchMock: jest.Mock<
Promise<Response>,
[input: RequestInfo | URL, init?: RequestInit | undefined]
>;
let solidLdoDataset: SolidLdoDataset;
beforeAll(async () => {
// Start up the server
app = await createApp();
await app.start();
authFetch = await getAuthenticatedFetch();
await authFetch(ROOT_CONTAINER, {
method: "POST",
headers: {
link: '<http://www.w3.org/ns/ldp#Container>; rel="type"',
slug: TEST_CONTAINER_SLUG,
},
});
});
afterAll(async () => {
app.stop();
});
beforeEach(async () => {
fetchMock = jest.fn(authFetch);
solidLdoDataset = createSolidLdoDataset({ fetch: fetchMock });
// Create a new document called sample.ttl
await Promise.all([
authFetch(TEST_CONTAINER_URI, {
method: "POST",
headers: { "content-type": "text/turtle", slug: "sample.ttl" },
body: SPIDER_MAN_TTL,
}),
authFetch(TEST_CONTAINER_URI, {
method: "POST",
headers: { "content-type": "text/plain", slug: "sample.txt" },
body: "some text.",
}),
]);
});
afterEach(async () => {
await Promise.all([
authFetch(SAMPLE_DATA_URI, {
method: "DELETE",
}),
authFetch(SAMPLE2_DATA_URI, {
method: "DELETE",
}),
authFetch(SAMPLE_BINARY_URI, {
method: "DELETE",
}),
authFetch(SAMPLE2_BINARY_URI, {
method: "DELETE",
}),
]);
});
describe("read", () => {
it("Reads a data leaf", async () => {
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
const result = await resource.read();
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("Reads a container", async () => {
const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI);
const result = await resource.read();
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();
expect(result.type).toBe("binaryReadSuccess");
expect(resource.isBinary()).toBe(true);
expect(await resource.getBlob()?.text()).toBe("some text.");
});
it("Returns an absent result if the document doesn't exist", async () => {
const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI);
const result = await resource.read();
expect(result.type).toBe("absentReadSuccess");
if (result.type !== "absentReadSuccess") return;
expect(result.resource.isAbsent()).toBe(true);
});
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();
expect(result.isError).toBe(true);
expect(result.type).toBe("serverError");
});
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();
expect(result.isError).toBe(true);
expect(result.type).toBe("unauthenticatedError");
});
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();
expect(result.isError).toBe(true);
expect(result.type).toBe("unexpectedHttpError");
});
it("Returns a NoncompliantPod error when no content type is returned", async () => {
fetchMock.mockResolvedValueOnce(
new Response(undefined, { status: 200, headers: {} }),
);
const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI);
const result = await resource.read();
expect(result.isError).toBe(true);
if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError");
expect(result.message).toBe(
"Response from https://solidweb.me/jackson3/test_ldo/sample2.ttl is not compliant with the Solid Specification: Resource requests must return a content-type header.",
);
});
it("Returns a NoncompliantPod error if invalid turtle is provided", async () => {
fetchMock.mockResolvedValueOnce(
new Response("Error", {
status: 200,
headers: new Headers({ "content-type": "text/turtle" }),
}),
);
const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI);
const result = await resource.read();
expect(result.isError).toBe(true);
if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError");
expect(result.message).toBe(
'Response from https://solidweb.me/jackson3/test_ldo/sample2.ttl is not compliant with the Solid Specification: Request returned noncompliant turtle: Unexpected "Error" on line 1.',
);
});
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();
expect(result.isError).toBe(true);
if (!result.isError) return;
expect(result.type).toBe("unexpectedResourceError");
expect(result.message).toBe("Something happened.");
});
it("Returns an error if there is no link header for a container request", async () => {
fetchMock.mockResolvedValueOnce(
new Response(SPIDER_MAN_TTL, {
status: 200,
headers: new Headers({ "content-type": "text/turtle" }),
}),
);
const resource = solidLdoDataset.getResource(TEST_CONTAINER_URI);
const result = await resource.read();
expect(result.isError).toBe(true);
if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError");
expect(result.message).toBe(
"Response from https://solidweb.me/jackson3/test_ldo/ is not compliant with the Solid Specification: No link header present in request.",
);
});
});
});

@ -0,0 +1,7 @@
[
{
"podName": "example",
"email": "hello@example.com",
"password": "abc123"
}
]

@ -7,26 +7,35 @@ import {
createDpopHeader, createDpopHeader,
generateDpopKeyPair, generateDpopKeyPair,
} from "@inrupt/solid-client-authn-core"; } from "@inrupt/solid-client-authn-core";
import type { App } from "@solid/community-server";
import { AppRunner, resolveModulePath } from "@solid/community-server"; import { AppRunner, resolveModulePath } from "@solid/community-server";
import "jest-rdf"; import "jest-rdf";
import fetch from "cross-fetch"; import fetch from "cross-fetch";
const config = [ const config = [
{ {
podName: process.env.USER_NAME, podName: process.env.USER_NAME || "example",
email: process.env.EMAIL, email: process.env.EMAIL || "hello@example.com",
password: process.env.PASSWORD, password: process.env.PASSWORD || "abc123",
}, },
]; ];
export const SERVER_DOMAIN = process.env.SERVER; export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/";
export const ROOT_COONTAINER = `${process.env.SERVER}${process.env.ROOT_CONTAINER}`; export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "example/";
export const ROOT_CONTAINER = `${SERVER_DOMAIN}${ROOT_ROUTE}`;
// Use an increased timeout, since the CSS server takes too much setup time. // Use an increased timeout, since the CSS server takes too much setup time.
jest.setTimeout(40_000); jest.setTimeout(40_000);
export function createApp() { export async function createApp(): Promise<App> {
return new AppRunner().create( if (process.env.SERVER) {
return {
start: () => {},
stop: () => {},
} as App;
}
const appRunner = new AppRunner();
return appRunner.create(
{ {
mainModulePath: resolveModulePath(""), mainModulePath: resolveModulePath(""),
typeChecking: false, typeChecking: false,
@ -98,9 +107,13 @@ export async function getAuthenticatedFetch() {
// Generate secret // Generate secret
const secret = await getSecret(); const secret = await getSecret();
if (!secret) throw new Error("No Secret");
// Get token // Get token
const token = await refreshToken(secret); const token = await refreshToken(secret);
if (!token) throw new Error("No Token");
// Build authenticated fetch // Build authenticated fetch
const authFetch = await buildAuthenticatedFetch(fetch, token.accessToken, { const authFetch = await buildAuthenticatedFetch(fetch, token.accessToken, {
dpopKey: token.dpopKey, dpopKey: token.dpopKey,

Loading…
Cancel
Save