UNPKG

@inrupt/solid-client

Version:

Make your web apps work with Solid Pods.

235 lines (203 loc) 7.57 kB
// Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // import { jest, describe, it, expect, beforeEach, afterEach, } from "@jest/globals"; import { foaf, rdf } from "rdf-namespaces"; import { DataFactory } from "n3"; import { isomorphic } from "rdf-isomorphic"; import type * as RDF from "@rdfjs/types"; import { getJsonLdParser } from "./jsonLd"; async function stringToArray(str: string) { const parser = getJsonLdParser(); const quadArr: RDF.Quad[] = []; await new Promise<void>((res, rej) => { parser.onQuad((quad) => quadArr.push(quad)); parser.onError(rej); parser.onComplete(res); parser.parse(str, { internal_resourceInfo: { sourceIri: "https://some.pod/resource", isRawData: false, linkedResources: {}, }, }); }); return quadArr; } const jsonLdUsername = ` { "@context":"https://pod.inrupt.com/solid/v1", "storage":"https://pod.inrupt.com/username/" }`; const jsonLdInvalidLiteral = ` { "@id":"https://example.com/some-path#someSubject", "@type":"http://xmlns.com/foaf/0.1/Person", "http://xmlns.com/foaf/0.1/name":“A literal with invalid quotes” }`; const jsonLdPersonData = ` { "@id":"https://example.com/some-path#someSubject", "@type":"http://xmlns.com/foaf/0.1/Person", "http://xmlns.com/foaf/0.1/name":"Some name" }`; const personQuads = [ DataFactory.quad( DataFactory.namedNode("https://example.com/some-path#someSubject"), DataFactory.namedNode(rdf.type), DataFactory.namedNode(foaf.Person), undefined, ), DataFactory.quad( DataFactory.namedNode("https://example.com/some-path#someSubject"), DataFactory.namedNode(foaf.name), DataFactory.literal("Some name"), undefined, ), ]; describe("The Parser", () => { let fetchSpy: jest.SpiedFunction<typeof fetch>; beforeEach(() => { fetchSpy = jest .spyOn(globalThis, "fetch") .mockImplementation(async () => new Response()); }); afterEach(() => { jest.restoreAllMocks(); }); it("should correctly find all triples in raw JSON-LD", async () => { const parser = getJsonLdParser(); const onQuadCallback = jest.fn<Parameters<typeof parser.onQuad>[0]>(); const onCompleteCallback = jest.fn(); parser.onQuad(onQuadCallback); parser.onComplete(onCompleteCallback); // FIXME: Despite the type signature, parser.parse does return a Promise, // so we await on it until we fix this behavior. await parser.parse(jsonLdPersonData, { internal_resourceInfo: { sourceIri: "https://example.com/some-path", isRawData: false, linkedResources: {}, }, }); // Our RDF parser will use a very specific implementation, which may use a // different RDF/JS implementation than our main code. This is no problem, // but we just need to make sure we use the RDF/JS 'quad equals' method // instead of the generic Jest `.toEqual()`, since it's RDF-quad-equality // we're checking, and not quad-implementation-equality. expect(onQuadCallback).toHaveBeenCalledTimes(2); expect( isomorphic( onQuadCallback.mock.calls.map(([quad]) => quad), personQuads, ), ).toBe(true); expect(onCompleteCallback).toHaveBeenCalledTimes(1); }); it("should reject if the JSON-LD is invalid", async () => { const parser = getJsonLdParser(); const onErrorCallback = jest.fn(); const onCompleteCallback = jest.fn(); parser.onError(onErrorCallback); parser.onComplete(onCompleteCallback); // FIXME: Despite the type signature, parser.parse does return a Promise, // so we await on it until we fix this behavior. await parser.parse(jsonLdInvalidLiteral, { internal_resourceInfo: { sourceIri: "https://example.com/some-path", isRawData: false, linkedResources: {}, }, }); expect(onErrorCallback).toHaveBeenCalledTimes(1); expect(onErrorCallback.mock.calls[0][0]).toBeInstanceOf(Error); }); describe("using custom fetcher for resolving contexts", () => { it("should resolve successfully", async () => { const parser = getJsonLdParser(); const onErrorCallback = jest.fn(); const onCompleteCallback = jest.fn(); parser.onError(onErrorCallback); parser.onComplete(onCompleteCallback); parser.onQuad(() => {}); fetchSpy.mockResolvedValueOnce( new Response( JSON.stringify({ "@context": { pim: "http://www.w3.org/ns/pim/space#", "@version": 1.1, "@protected": true, id: "@id", type: "@type", storage: { "@id": "pim:storage", "@type": "@id" }, }, }), { headers: { "Content-Type": "application/ld+json" } }, ), ); // FIXME: Despite the type signature, parser.parse does return a Promise, // so we await on it until we fix this behavior. await parser.parse(jsonLdUsername, { internal_resourceInfo: { sourceIri: "https://some.pod/resource", isRawData: false, linkedResources: {}, }, }); expect(onErrorCallback).toHaveBeenCalledTimes(0); expect(onCompleteCallback).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledTimes(1); }); it("should handle errors gracefully", async () => { const parser = getJsonLdParser(); const onErrorCallback = jest.fn(); const onCompleteCallback = jest.fn(); parser.onError(onErrorCallback); parser.onComplete(onCompleteCallback); fetchSpy.mockRejectedValueOnce("Some error"); // FIXME: Despite the type signature, parser.parse does return a Promise, // so we await on it until we fix this behavior. await parser.parse(jsonLdUsername, { internal_resourceInfo: { sourceIri: "https://some.pod/resource", isRawData: false, linkedResources: {}, }, }); expect(onErrorCallback).toHaveBeenCalledTimes(1); expect(onCompleteCallback).toHaveBeenCalledTimes(1); expect(fetchSpy).toHaveBeenCalledTimes(1); }); it("Should parse valid JSON-LD to correct quads", async () => { expect( isomorphic(await stringToArray(jsonLdPersonData), personQuads), ).toBe(true); }); it("Should throw error before complete on invalid JSON-LD", () => { return expect(stringToArray(jsonLdInvalidLiteral)).rejects.toThrow(); }); }); });