UNPKG

@inrupt/solid-client

Version:
983 lines (858 loc) • 33.2 kB
/** * Copyright 2020 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 } from "@jest/globals"; jest.mock("../fetcher.ts", () => ({ fetch: jest.fn(window.fetch).mockImplementation(() => Promise.resolve( new Response(undefined, { headers: { Location: "https://arbitrary.pod/resource" }, }) ) ), })); import { Response } from "cross-fetch"; import { acp, rdf } from "../constants"; import * as SolidDatasetModule from "../resource/solidDataset"; import * as FileModule from "../resource/file"; import * as ResourceModule from "../resource/resource"; import { getFileWithAccessDatasets, getFileWithAcr, getReferencedPolicyUrlAll, getResourceInfoWithAccessDatasets, getResourceInfoWithAcr, getSolidDatasetWithAccessDatasets, getSolidDatasetWithAcr, saveAcrFor, } from "./acp"; import { UrlString, WithServerResourceInfo, File } from "../interfaces"; import { createThing, setThing } from "../thing/thing"; import { addIri } from "../thing/add"; import { AccessControlResource } from "./control"; import { mockSolidDatasetFrom } from "../resource/mock"; import { addMockAcrTo } from "./mock"; const defaultMockPolicies = { policies: ["https://some.pod/policies#policy"], memberPolicies: ["https://some.pod/policies#memberPolicy"], acrPolicies: [] as string[], memberAcrPolicies: [] as string[], }; function mockAcr(accessTo: UrlString, policies = defaultMockPolicies) { let control = createThing({ name: "access-control" }); control = addIri(control, rdf.type, acp.AccessControl); policies.policies.forEach((policyUrl) => { control = addIri(control, acp.apply, policyUrl); }); policies.memberPolicies.forEach((policyUrl) => { control = addIri(control, acp.applyMembers, policyUrl); }); const acrUrl = accessTo + "?ext=acr"; let acrThing = createThing({ url: acrUrl }); policies.acrPolicies.forEach((policyUrl) => { acrThing = addIri(acrThing, acp.access, policyUrl); }); policies.memberAcrPolicies.forEach((policyUrl) => { acrThing = addIri(acrThing, acp.accessMembers, policyUrl); }); let acr: AccessControlResource & WithServerResourceInfo = Object.assign( mockSolidDatasetFrom(acrUrl), { accessTo: accessTo, } ); acr = setThing(acr, control); acr = setThing(acr, acrThing); return acr; } describe("getSolidDatasetWithAcr", () => { it("calls the included fetcher by default", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; await getSolidDatasetWithAcr("https://some.pod/resource"); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource" ); }); it("uses the given fetcher if provided", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue(new Response()); await getSolidDatasetWithAcr("https://some.pod/resource", { fetch: mockFetch, }); expect(mockFetch.mock.calls[0][0]).toEqual("https://some.pod/resource"); }); it("returns null for the ACR if it is not accessible to the current user", async () => { const mockFetch = jest .fn(window.fetch) .mockResolvedValueOnce( new Response(undefined, { headers: { Link: `<https://some.pod/acr.ttl>; rel="${acp.accessControl}"`, }, url: "https://some.pod/resource", } as ResponseInit) ) .mockResolvedValueOnce(new Response("Not allowed", { status: 401 })); const fetchedDataset = await getSolidDatasetWithAcr( "https://some.pod/resource", { fetch: mockFetch } ); expect(mockFetch.mock.calls[0][0]).toEqual("https://some.pod/resource"); expect(mockFetch.mock.calls[1][0]).toEqual("https://some.pod/acr.ttl"); expect(fetchedDataset.internal_acp.acr).toBeNull(); }); it("attaches the fetched ACR to the returned SolidDataset", async () => { const mockedSolidDataset = mockSolidDatasetFrom( "https://arbitrary.pod/resource" ); mockedSolidDataset.internal_resourceInfo.linkedResources = { [acp.accessControl]: ["https://arbitrary.pod/resource?ext=acr"], }; const mockedAcr = mockAcr("https://arbitrary.pod/resource", { policies: [], memberPolicies: [], acrPolicies: [], memberAcrPolicies: [], }); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); mockedGetSolidDataset .mockResolvedValueOnce(mockedSolidDataset) .mockResolvedValueOnce(mockedAcr); const fetchedDataset = await getSolidDatasetWithAcr( "https://some.pod/resource" ); expect(fetchedDataset.internal_acp.acr).toBe(mockedAcr); }); }); describe("getFileWithAcr", () => { it("calls the included fetcher by default", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; await getFileWithAcr("https://some.pod/resource"); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource" ); }); it("uses the given fetcher if provided", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue(new Response()); await getFileWithAcr("https://some.pod/resource", { fetch: mockFetch, }); expect(mockFetch.mock.calls[0][0]).toEqual("https://some.pod/resource"); }); it("returns null for the ACR if it is not accessible to the current user", async () => { const mockFetch = jest .fn(window.fetch) .mockResolvedValueOnce( new Response(undefined, { headers: { Link: `<https://some.pod/acr.ttl>; rel="${acp.accessControl}"`, }, url: "https://some.pod/resource", } as ResponseInit) ) .mockResolvedValueOnce(new Response("Not allowed", { status: 401 })); const fetchedFile = await getFileWithAcr("https://some.pod/resource", { fetch: mockFetch, }); expect(mockFetch.mock.calls[0][0]).toEqual("https://some.pod/resource"); expect(mockFetch.mock.calls[1][0]).toEqual("https://some.pod/acr.ttl"); expect(fetchedFile.internal_acp.acr).toBeNull(); }); it("attaches the fetched ACR to the returned File", async () => { const mockedFile: File & WithServerResourceInfo = Object.assign( new Blob(), { internal_resourceInfo: { sourceIri: "https://some.pod/resource", isRawData: true, linkedResources: { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }, }, } ); const mockedAcr = mockAcr("https://arbitrary.pod/resource", { policies: [], memberPolicies: [], acrPolicies: [], memberAcrPolicies: [], }); const mockedGetFile = jest.spyOn(FileModule, "getFile"); mockedGetFile.mockResolvedValueOnce(mockedFile); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); mockedGetSolidDataset.mockResolvedValueOnce(mockedAcr); const fetchedFile = await getFileWithAcr("https://some.pod/resource"); expect(fetchedFile.internal_acp.acr).toBe(mockedAcr); }); }); describe("getResourceInfoWithAcr", () => { it("calls the included fetcher by default", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; await getResourceInfoWithAcr("https://some.pod/resource"); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource" ); }); it("uses the given fetcher if provided", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue(new Response()); await getResourceInfoWithAcr("https://some.pod/resource", { fetch: mockFetch, }); expect(mockFetch.mock.calls[0][0]).toEqual("https://some.pod/resource"); }); it("attaches the fetched ACR to the returned ResourceInfo", async () => { const mockedResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { sourceIri: "https://arbitrary.pod/resource", isRawData: true, linkedResources: { [acp.accessControl]: ["https://arbitrary.pod/resource?ext=acr"], }, }, }; const mockedAcr = mockAcr("https://arbitrary.pod/resource", { policies: [], memberPolicies: [], acrPolicies: [], memberAcrPolicies: [], }); const mockedGetResourceInfo = jest.spyOn(ResourceModule, "getResourceInfo"); mockedGetResourceInfo.mockResolvedValueOnce(mockedResourceInfo); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); mockedGetSolidDataset.mockResolvedValueOnce(mockedAcr); const fetchedResourceInfo = await getResourceInfoWithAcr( "https://some.pod/resource" ); expect(fetchedResourceInfo.internal_acp.acr).toBe(mockedAcr); }); it("returns null for the ACR if it is not accessible to the current user", async () => { const mockFetch = jest .fn(window.fetch) .mockResolvedValueOnce( new Response(undefined, { headers: { Link: `<https://some.pod/acr.ttl>; rel="${acp.accessControl}"`, }, url: "https://some.pod/resource", } as ResponseInit) ) .mockResolvedValueOnce(new Response("Not allowed", { status: 401 })); const fetchedResourceInfo = await getResourceInfoWithAcr( "https://some.pod/resource", { fetch: mockFetch } ); expect(mockFetch.mock.calls[0][0]).toEqual("https://some.pod/resource"); expect(mockFetch.mock.calls[1][0]).toEqual("https://some.pod/acr.ttl"); expect(fetchedResourceInfo.internal_acp.acr).toBeNull(); }); }); describe("getReferencedPolicyUrlAll", () => { it("returns an empty Object if no APRs were referenced", async () => { const mockedResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { sourceIri: "https://arbitrary.pod/resource", isRawData: true, linkedResources: { [acp.accessControl]: ["https://arbitrary.pod/resource?ext=acr"], }, }, }; const mockedAcr = mockAcr("https://arbitrary.pod/resource", { policies: [], memberPolicies: [], acrPolicies: [], memberAcrPolicies: [], }); const withMockedAcr = addMockAcrTo(mockedResourceInfo, mockedAcr); const policyUrls = getReferencedPolicyUrlAll(withMockedAcr); expect(policyUrls).toHaveLength(0); }); it("only includes one mention of a Resource that was referenced multiple times", async () => { const mockedResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { sourceIri: "https://some.pod/resource", isRawData: true, linkedResources: { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }, }, }; const mockedAcr = mockAcr("https://some.pod/resource", { policies: ["https://some.pod/policy-resource#a-policy"], memberPolicies: [ "https://some.pod/policy-resource#a-member-policy", "https://some.pod/policy-resource#another-member-policy", ], acrPolicies: [], memberAcrPolicies: [], }); const withMockedAcr = addMockAcrTo(mockedResourceInfo, mockedAcr); const policyUrls = getReferencedPolicyUrlAll(withMockedAcr); expect(policyUrls).toEqual(["https://some.pod/policy-resource"]); }); it("includes all referenced Policy Resources", async () => { const mockedResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { sourceIri: "https://some.pod/resource", isRawData: true, linkedResources: { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }, }, }; const mockedAcr = mockAcr("https://some.pod/resource", { policies: ["https://some.pod/policy-resource#a-policy"], memberPolicies: [ "https://some.pod/policy-resource#a-member-policy", "https://some.pod/other-policy-resource#another-member-policy", ], acrPolicies: [], memberAcrPolicies: [], }); const withMockedAcr = addMockAcrTo(mockedResourceInfo, mockedAcr); const policyUrls = getReferencedPolicyUrlAll(withMockedAcr); expect(policyUrls).toEqual([ "https://some.pod/policy-resource", "https://some.pod/other-policy-resource", ]); }); it("includes referenced ACR Policy Resources", async () => { const mockedResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { sourceIri: "https://some.pod/resource", isRawData: true, linkedResources: { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }, }, }; const mockedAcr = mockAcr("https://some.pod/resource", { policies: [], memberPolicies: [], acrPolicies: ["https://some.pod/policy-resource#an-acr-policy"], memberAcrPolicies: [ "https://some.pod/other-policy-resource#another-acr-policy", ], }); const withMockedAcr = addMockAcrTo(mockedResourceInfo, mockedAcr); const policyUrls = getReferencedPolicyUrlAll(withMockedAcr); expect(policyUrls).toEqual([ "https://some.pod/policy-resource", "https://some.pod/other-policy-resource", ]); }); }); describe("getSolidDatasetWithAccessDatasets", () => { it("fetches the Resource at the given URL", async () => { const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); await getSolidDatasetWithAccessDatasets("https://some.pod/resource"); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource", expect.anything() ); }); it("fetches the ACL when the SolidDataset at the given URL exposes one", async () => { const mockDataset = mockSolidDatasetFrom("https://arbitrary.pod/resource"); mockDataset.internal_resourceInfo.aclUrl = "https://some.pod/resource.acl"; mockDataset.internal_resourceInfo.linkedResources = { acl: ["https://some.pod/resource.acl"], }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); mockedGetSolidDataset.mockResolvedValueOnce(mockDataset); await getSolidDatasetWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(2); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource.acl", expect.anything() ); }); it("fetches the ACR when the SolidDataset at the given URL exposes one", async () => { const mockDataset = mockSolidDatasetFrom("https://arbitrary.pod/resource"); mockDataset.internal_resourceInfo.linkedResources = { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); mockedGetSolidDataset.mockResolvedValueOnce(mockDataset); await getSolidDatasetWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(2); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource?ext=acr", expect.anything() ); }); it("does not fetch any Access Dataset if none is exposed", async () => { const mockDataset = mockSolidDatasetFrom("https://arbitrary.pod/resource"); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); mockedGetSolidDataset.mockResolvedValueOnce(mockDataset); await getSolidDatasetWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); }); it("passes on the given fetcher to the Resource and ACL fetcher", async () => { const mockDataset = mockSolidDatasetFrom("https://some.pod/resource"); mockDataset.internal_resourceInfo.aclUrl = "https://some.pod/resource.acl"; mockDataset.internal_resourceInfo.linkedResources = { acl: ["https://some.pod/resource.acl"], }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); mockedGetSolidDataset.mockResolvedValueOnce(mockDataset); const mockedFetcher = jest .fn(window.fetch) .mockResolvedValue(new Response()); await getSolidDatasetWithAccessDatasets("https://some.pod/resource", { fetch: mockedFetcher, }); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(2); expect(mockedGetSolidDataset).toHaveBeenNthCalledWith( 1, "https://some.pod/resource", expect.objectContaining({ fetch: mockedFetcher }) ); expect(mockedGetSolidDataset).toHaveBeenNthCalledWith( 2, "https://some.pod/resource.acl", expect.objectContaining({ fetch: mockedFetcher }) ); }); it("passes on the given fetcher to the Resource and ACR fetcher", async () => { const mockDataset = mockSolidDatasetFrom("https://some.pod/resource"); mockDataset.internal_resourceInfo.linkedResources = { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); mockedGetSolidDataset.mockResolvedValueOnce(mockDataset); const mockedFetcher = jest .fn(window.fetch) .mockResolvedValue(new Response()); await getSolidDatasetWithAccessDatasets("https://some.pod/resource", { fetch: mockedFetcher, }); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(2); expect(mockedGetSolidDataset).toHaveBeenNthCalledWith( 1, "https://some.pod/resource", expect.objectContaining({ fetch: mockedFetcher }) ); expect(mockedGetSolidDataset).toHaveBeenNthCalledWith( 2, "https://some.pod/resource?ext=acr", expect.objectContaining({ fetch: mockedFetcher }) ); }); }); describe("getFileWithAccessDatasets", () => { it("fetches the Resource at the given URL", async () => { const mockedGetFile = jest.spyOn(FileModule, "getFile"); await getFileWithAccessDatasets("https://some.pod/resource"); expect(mockedGetFile).toHaveBeenCalledTimes(1); expect(mockedGetFile).toHaveBeenLastCalledWith( "https://some.pod/resource", expect.anything() ); }); it("fetches the ACL when the File at the given URL exposes one", async () => { const mockFile = Object.assign(new Blob(), { internal_resourceInfo: { aclUrl: "https://some.pod/resource.acl", linkedResources: { acl: ["https://some.pod/resource.acl"] }, sourceIri: "https://arbitrary.pod/resource", isRawData: true, url: "https://arbitrary.pod/resource", }, }); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetFile = jest.spyOn(FileModule, "getFile"); mockedGetFile.mockResolvedValueOnce(mockFile); await getFileWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource.acl", expect.anything() ); }); it("fetches the ACR when the File at the given URL exposes one", async () => { const mockFile = Object.assign(new Blob(), { internal_resourceInfo: { linkedResources: { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }, sourceIri: "https://arbitrary.pod/resource", isRawData: true, }, }); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetFile = jest.spyOn(FileModule, "getFile"); mockedGetFile.mockResolvedValueOnce(mockFile); await getFileWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource?ext=acr", expect.anything() ); }); it("does not fetch any Access Dataset if none is exposed", async () => { const mockFile = Object.assign(new Blob(), { internal_resourceInfo: { linkedResources: {}, sourceIri: "https://arbitrary.pod/resource", isRawData: true, }, }); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetFile = jest.spyOn(FileModule, "getFile"); mockedGetFile.mockResolvedValueOnce(mockFile); await getFileWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).not.toHaveBeenCalled(); }); it("passes on the given fetcher to the Resource and ACL fetcher", async () => { const mockFile = Object.assign(new Blob(), { internal_resourceInfo: { aclUrl: "https://some.pod/resource.acl", linkedResources: { acl: ["https://some.pod/resource.acl"] }, sourceIri: "https://arbitrary.pod/resource", isRawData: true, }, }); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetFile = jest.spyOn(FileModule, "getFile"); mockedGetFile.mockResolvedValueOnce(mockFile); const mockedFetcher = jest .fn(window.fetch) .mockResolvedValue(new Response()); await getFileWithAccessDatasets("https://some.pod/resource", { fetch: mockedFetcher, }); expect(mockedGetFile).toHaveBeenCalledTimes(1); expect(mockedGetFile).toHaveBeenLastCalledWith( "https://some.pod/resource", expect.objectContaining({ fetch: mockedFetcher }) ); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource.acl", expect.objectContaining({ fetch: mockedFetcher }) ); }); it("passes on the given fetcher to the Resource and ACR fetcher", async () => { const mockFile = Object.assign(new Blob(), { internal_resourceInfo: { linkedResources: { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }, sourceIri: "https://arbitrary.pod/resource", isRawData: true, }, }); const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetFile = jest.spyOn(FileModule, "getFile"); mockedGetFile.mockResolvedValueOnce(mockFile); const mockedFetcher = jest .fn(window.fetch) .mockResolvedValue(new Response()); await getFileWithAccessDatasets("https://some.pod/resource", { fetch: mockedFetcher, }); expect(mockedGetFile).toHaveBeenCalledTimes(1); expect(mockedGetFile).toHaveBeenLastCalledWith( "https://some.pod/resource", expect.objectContaining({ fetch: mockedFetcher }) ); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource?ext=acr", expect.objectContaining({ fetch: mockedFetcher }) ); }); }); describe("getResourceInfoWithAccessDatasets", () => { it("fetches the ResourceInfo for the given URL", async () => { const mockedGetResourceInfo = jest.spyOn(ResourceModule, "getResourceInfo"); await getResourceInfoWithAccessDatasets("https://some.pod/resource"); expect(mockedGetResourceInfo).toHaveBeenCalledTimes(1); expect(mockedGetResourceInfo).toHaveBeenLastCalledWith( "https://some.pod/resource", expect.anything() ); }); it("fetches the ACL when the Resource at the given URL exposes one", async () => { const mockResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { aclUrl: "https://some.pod/resource.acl", linkedResources: { acl: ["https://some.pod/resource.acl"] }, sourceIri: "https://arbitrary.pod/resource", isRawData: true, }, }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetResourceInfo = jest.spyOn(ResourceModule, "getResourceInfo"); mockedGetResourceInfo.mockResolvedValueOnce(mockResourceInfo); await getResourceInfoWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource.acl", expect.anything() ); }); it("fetches the ACR when the Resource at the given URL exposes one", async () => { const mockResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { linkedResources: { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }, sourceIri: "https://arbitrary.pod/resource", isRawData: true, }, }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetResourceInfo = jest.spyOn(ResourceModule, "getResourceInfo"); mockedGetResourceInfo.mockResolvedValueOnce(mockResourceInfo); await getResourceInfoWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource?ext=acr", expect.anything() ); }); it("does not fetch any Access Dataset if none is exposed", async () => { const mockResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { linkedResources: {}, sourceIri: "https://arbitrary.pod/resource", isRawData: true, }, }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetResourceInfo = jest.spyOn(ResourceModule, "getResourceInfo"); mockedGetResourceInfo.mockResolvedValueOnce(mockResourceInfo); await getResourceInfoWithAccessDatasets("https://arbitrary.pod/resource"); expect(mockedGetSolidDataset).not.toHaveBeenCalled(); }); it("passes on the given fetcher to the ResourceInfo and ACL fetcher", async () => { const mockResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { aclUrl: "https://some.pod/.acl", linkedResources: { acl: ["https://some.pod/.acl"] }, sourceIri: "https://some.pod/", isRawData: true, }, }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetResourceInfo = jest.spyOn(ResourceModule, "getResourceInfo"); mockedGetResourceInfo.mockResolvedValueOnce(mockResourceInfo); const mockedFetcher = jest .fn(window.fetch) .mockResolvedValue(new Response()); // We specifically use the root Resource here, // because otherwise `getResourceInfo` would be called on the parent Resource // to find a fallback ACL: await getResourceInfoWithAccessDatasets("https://some.pod/", { fetch: mockedFetcher, }); expect(mockedGetResourceInfo).toHaveBeenCalledTimes(1); expect(mockedGetResourceInfo).toHaveBeenLastCalledWith( "https://some.pod/", expect.objectContaining({ fetch: mockedFetcher }) ); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/.acl", expect.objectContaining({ fetch: mockedFetcher }) ); }); it("passes on the given fetcher to the ResourceInfo and ACR fetcher", async () => { const mockResourceInfo: WithServerResourceInfo = { internal_resourceInfo: { linkedResources: { [acp.accessControl]: ["https://some.pod/resource?ext=acr"], }, sourceIri: "https://some.pod/resource", isRawData: true, }, }; const mockedGetSolidDataset = jest.spyOn( SolidDatasetModule, "getSolidDataset" ); const mockedGetResourceInfo = jest.spyOn(ResourceModule, "getResourceInfo"); mockedGetResourceInfo.mockResolvedValueOnce(mockResourceInfo); const mockedFetcher = jest .fn(window.fetch) .mockResolvedValue(new Response()); await getResourceInfoWithAccessDatasets("https://some.pod/resource", { fetch: mockedFetcher, }); expect(mockedGetResourceInfo).toHaveBeenCalledTimes(1); expect(mockedGetResourceInfo).toHaveBeenLastCalledWith( "https://some.pod/resource", expect.objectContaining({ fetch: mockedFetcher }) ); expect(mockedGetSolidDataset).toHaveBeenCalledTimes(1); expect(mockedGetSolidDataset).toHaveBeenLastCalledWith( "https://some.pod/resource?ext=acr", expect.objectContaining({ fetch: mockedFetcher }) ); }); }); describe("saveAcrFor", () => { it("calls the included fetcher by default", async () => { const mockedResponse = new Response(); jest .spyOn(mockedResponse, "url", "get") .mockReturnValue("https://arbitrary.pod/resource"); const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; mockedFetcher.fetch.mockResolvedValue(mockedResponse); const mockedAcr = mockAcr("https://arbitrary.pod/resource"); const mockedResource = addMockAcrTo( mockSolidDatasetFrom("https://arbitrary.pod/resource"), mockedAcr ); await saveAcrFor(mockedResource); expect(mockedFetcher.fetch).toHaveBeenCalledTimes(1); }); it("uses the given fetcher if provided", async () => { const mockedResponse = new Response(); jest .spyOn(mockedResponse, "url", "get") .mockReturnValue("https://arbitrary.pod/resource"); const mockFetch = jest.fn(window.fetch).mockResolvedValue(mockedResponse); const mockedAcr = mockAcr("https://arbitrary.pod/resource"); const mockedResource = addMockAcrTo( mockSolidDatasetFrom("https://arbitrary.pod/resource"), mockedAcr ); await saveAcrFor(mockedResource, { fetch: mockFetch, }); expect(mockFetch).toHaveBeenCalledTimes(1); }); it("sends the ACR to the Pod", async () => { const mockedResponse = new Response(); jest .spyOn(mockedResponse, "url", "get") .mockReturnValue("https://arbitrary.pod/resource"); const mockFetch = jest.fn(window.fetch).mockResolvedValue(mockedResponse); const mockedSaveSolidDatasetAt = jest.spyOn( SolidDatasetModule, "saveSolidDatasetAt" ); const mockedAcr = mockAcr("https://some.pod/resource"); const mockedResource = addMockAcrTo( mockSolidDatasetFrom("https://some.pod/resource"), mockedAcr ); await saveAcrFor(mockedResource, { fetch: mockFetch, }); expect(mockedSaveSolidDatasetAt).toHaveBeenCalledTimes(1); expect(mockedSaveSolidDatasetAt).toHaveBeenCalledWith( "https://some.pod/resource?ext=acr", mockedAcr, expect.objectContaining({ fetch: mockFetch }) ); }); it("attaches the saved ACR to the returned Resource", async () => { const mockedSaveSolidDatasetAt = jest.spyOn( SolidDatasetModule, "saveSolidDatasetAt" ); const mockedAcr = mockAcr("https://some.pod/resource"); const mockedResource = addMockAcrTo( mockSolidDatasetFrom("https://some.pod/resource"), mockedAcr ); const fakeReturnedAcr = { fake: "ACR" } as any; mockedSaveSolidDatasetAt.mockResolvedValueOnce(fakeReturnedAcr); const savedResource = await saveAcrFor(mockedResource); expect(savedResource.internal_acp.acr).toEqual(fakeReturnedAcr); }); });