UNPKG

@inrupt/solid-client

Version:
1,763 lines (1,589 loc) • 102 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 } from "@jest/globals"; import { IriString, SolidDataset, WithServerResourceInfo } from "../interfaces"; import { getAgentResourceAccess } from "../acl/agent"; import { getAgentAccess, getAgentAccessAll, getGroupAccess, getGroupAccessAll, getPublicAccess, setAgentResourceAccess, setGroupResourceAccess, setPublicResourceAccess, WacAccess, } from "./wac"; import { Response } from "cross-fetch"; import { triplesToTurtle } from "../formats/turtle"; import { addMockAclRuleQuads } from "../acl/mock.internal"; import { acl, foaf } from "../constants"; import { setMockAclUrl } from "../acl/mock.internal"; jest.mock("../fetcher.ts", () => ({ fetch: jest.fn().mockImplementation(() => Promise.resolve( new Response(undefined, { headers: { Location: "https://arbitrary.pod/resource" }, }) ) ), })); import { mockSolidDatasetFrom } from "../resource/mock"; import { AgentAccess } from "../acl/agent"; import { internal_getResourceAcl } from "../acl/acl.internal"; import { AclDataset } from "../acl/acl"; import { getGroupResourceAccess } from "../acl/group"; import { getPublicResourceAccess } from "../acl/class"; function getMockDataset( sourceIri: IriString, aclIri?: IriString ): SolidDataset & WithServerResourceInfo { const result = mockSolidDatasetFrom(sourceIri); if (aclIri === undefined) { return result; } return setMockAclUrl(result, aclIri); } function mockResponse( body?: BodyInit | null, init?: ResponseInit & { url: string } ): Response { return new Response(body, init); } describe("getAgentAccess", () => { it("calls the included fetcher by default", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await getAgentAccess(resource, "https://some.pod/profile#agent"); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource.acl" ); }); it("returns null if no ACL is accessible", async () => { const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("returns null if no ACL is advertised by the target resource", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("ACL not found", { status: 404, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset("https://some.pod/resource"); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("fetches the resource ACL if available", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#agent", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource" ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: true, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("fetches the fallback ACL if no resource ACL is available", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/.acl"), "https://some.pod/profile#agent", "https://some.pod/", { read: true, append: false, write: false, control: false }, "default" ); const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: true, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("ignores the fallback ACL if the resource ACL is available", async () => { const fallbackAclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/.acl"), "https://some.pod/profile#agent", "https://some.pod/", { read: false, append: true, write: false, control: false }, "default" ); const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#agent", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource" ); const mockFetch = jest .fn(window.fetch) // The resource ACL is available... .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(fallbackAclResource)), { status: 200, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ append: false, read: true, write: false, controlRead: false, controlWrite: false, }); }); it("returns true for both controlRead and controlWrite if the Agent has control access", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#agent", "https://some.pod/resource", { read: false, append: false, write: false, control: true }, "resource" ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: true, controlWrite: true, }); }); it("correctly reads the Agent append access", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#agent", "https://some.pod/resource", { read: false, append: true, write: false, control: false }, "resource" ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: true, write: false, controlRead: false, controlWrite: false, }); }); it("correctly reads the Agent write access, which implies append", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#agent", "https://some.pod/resource", { read: false, append: false, write: true, control: false }, "resource" ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: true, write: true, controlRead: false, controlWrite: false, }); }); it("returns false for all modes the Agent isn't present", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#another-agent", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource" ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for groups", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#agent", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentGroup ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for everyone", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), foaf.Agent, "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for authenticated agents", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), acl.AuthenticatedAgent, "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccess(resource, "https://some.pod/profile#agent", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); }); describe("getGroupAccess", () => { it("calls the included fetcher by default", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await getGroupAccess(resource, "https://some.pod/groups#group"); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource.acl" ); }); it("returns null if no ACL is accessible", async () => { const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("returns null if no ACL is advertised by the target resource", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("ACL not found", { status: 404, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset("https://some.pod/resource"); const result = getAgentAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("fetches the resource ACL if available", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/groups#group", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentGroup ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: true, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("fetches the fallback ACL if no resource ACL is available", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/.acl"), "https://some.pod/groups#group", "https://some.pod/", { read: true, append: false, write: false, control: false }, "default", "https://some.pod/resource.acl#some-rule", acl.agentGroup ); const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: true, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("ignores the fallback ACL if the resource ACL is available", async () => { const fallbackAclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/.acl"), "https://some.pod/groups#group", "https://some.pod/", { read: true, append: false, write: false, control: false }, "default", "https://some.pod/resource.acl#some-rule", acl.agentGroup ); const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/groups#group", "https://some.pod/resource", { read: false, append: true, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentGroup ); const mockFetch = jest .fn(window.fetch) // The resource ACL is available... .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(fallbackAclResource)), { status: 200, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ append: true, read: false, write: false, controlRead: false, controlWrite: false, }); }); it("returns an empty object if the group isn't present", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/groups#another-group", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentGroup ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for agents", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/groups#group", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agent ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for everyone", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), foaf.Agent, "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for authenticated agents", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), acl.AuthenticatedAgent, "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccess(resource, "https://some.pod/groups#group", { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); }); describe("getPublicAccess", () => { it("calls the included fetcher by default", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await getPublicAccess(resource); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource.acl" ); }); it("returns null if no ACL is accessible", async () => { const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("returns null if no ACL is advertised by the target resource", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("ACL not found", { status: 404, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset("https://some.pod/resource"); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("fetches the resource ACL if available", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), foaf.Agent, "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: true, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("fetches the fallback ACL if no resource ACL is available", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/.acl"), foaf.Agent, "https://some.pod/", { read: true, append: false, write: false, control: false }, "default", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: true, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("ignores the fallback ACL if the resource ACL is available", async () => { const fallbackAclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/.acl"), foaf.Agent, "https://some.pod/", { read: true, append: false, write: false, control: false }, "default", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), foaf.Agent, "https://some.pod/resource", { read: false, append: true, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest .fn(window.fetch) // The resource ACL is available... .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse(await triplesToTurtle(Array.from(fallbackAclResource)), { status: 200, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ append: true, read: false, write: false, controlRead: false, controlWrite: false, }); }); it("returns an empty object if no public access is specified", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), foaf.Agent, "https://some.pod/resource", { read: false, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for agents", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), foaf.Agent, "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agent ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for groups", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), foaf.Agent, "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentGroup ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); it("does not return access for authenticated agents", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), acl.AuthenticatedAgent, "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#some-rule", acl.agentClass ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getPublicAccess(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ read: false, append: false, write: false, controlRead: false, controlWrite: false, }); }); }); describe("getAgentAccessAll", () => { it("uses the default fetcher if none is provided", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await getAgentAccessAll(resource); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource.acl" ); }); it("returns null if the advertized ACL isn't accessible", async () => { const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccessAll(resource, { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("returns null if no ACL is advertised by the resource", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("ACL not found", { status: 404, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset("https://some.pod/resource"); const result = getAgentAccessAll(resource, { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("calls the underlying getAgentAccessAll", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("", { status: 200, url: "https://some.pod/resource.acl", }) ); const wacModule = jest.requireActual("../acl/agent") as { getAgentAccessAll: () => Promise<AgentAccess>; }; const getAgentAccessAllWac = jest.spyOn(wacModule, "getAgentAccessAll"); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await getAgentAccessAll(resource, { fetch: mockFetch, }); expect(getAgentAccessAllWac).toHaveBeenCalled(); }); it("returns an empty list if the ACL defines no access", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("", { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await expect( getAgentAccessAll(resource, { fetch: mockFetch, }) ).resolves.toStrictEqual({}); }); it("returns the access set for all the actors present", async () => { let aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#agent-a", "https://some.pod/resource", { read: false, append: false, write: true, control: false }, "resource" ); aclResource = addMockAclRuleQuads( aclResource, "https://some.pod/profile#agent-b", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource" ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await expect( getAgentAccessAll(resource, { fetch: mockFetch, }) ).resolves.toStrictEqual({ "https://some.pod/profile#agent-a": { read: false, append: true, write: true, controlRead: false, controlWrite: false, }, "https://some.pod/profile#agent-b": { read: true, append: false, write: false, controlRead: false, controlWrite: false, }, }); }); it("returns true for both controlRead and controlWrite if an Agent has control access", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/profile#agent", "https://some.pod/resource", { read: false, append: false, write: false, control: true }, "resource" ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getAgentAccessAll(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ "https://some.pod/profile#agent": { read: false, append: false, write: false, controlRead: true, controlWrite: true, }, }); }); }); describe("getGroupAccessAll", () => { it("uses the default fetcher if none is provided", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await getGroupAccessAll(resource); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource.acl" ); }); it("returns null if the advertized ACL isn't accessible", async () => { const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccessAll(resource, { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("returns null if no ACL is advertised by the resource", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("ACL not found", { status: 404, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset("https://some.pod/resource"); const result = getGroupAccessAll(resource, { fetch: mockFetch, }); await expect(result).resolves.toBeNull(); }); it("calls the underlying getGroupAccessAll", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("", { status: 200, url: "https://some.pod/resource.acl", }) ); const wacModule = jest.requireActual("../acl/group") as { getGroupAccessAll: () => Promise<AgentAccess>; }; const getGroupAccessAllWac = jest.spyOn(wacModule, "getGroupAccessAll"); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await getGroupAccessAll(resource, { fetch: mockFetch, }); expect(getGroupAccessAllWac).toHaveBeenCalled(); }); it("returns an empty list if the ACL defines no access", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("", { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await expect( getGroupAccessAll(resource, { fetch: mockFetch, }) ).resolves.toStrictEqual({}); }); it("returns the access set for all the actors present", async () => { let aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/groups#group-a", "https://some.pod/resource", { read: false, append: false, write: true, control: false }, "resource", "https://some.pod/resource.acl#rule-a", acl.agentGroup ); aclResource = addMockAclRuleQuads( aclResource, "https://some.pod/groups#group-b", "https://some.pod/resource", { read: true, append: false, write: false, control: false }, "resource", "https://some.pod/resource.acl#rule-b", acl.agentGroup ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await expect( getGroupAccessAll(resource, { fetch: mockFetch, }) ).resolves.toStrictEqual({ "https://some.pod/groups#group-a": { read: false, append: true, write: true, controlRead: false, controlWrite: false, }, "https://some.pod/groups#group-b": { read: true, append: false, write: false, controlRead: false, controlWrite: false, }, }); }); it("returns true for both controlRead and controlWrite if a Group has control access", async () => { const aclResource = addMockAclRuleQuads( getMockDataset("https://some.pod/resource.acl"), "https://some.pod/groups#group", "https://some.pod/resource", { read: false, append: false, write: false, control: true }, "resource", "https://some.pod/resource.acl#rule", acl.agentGroup ); const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse(await triplesToTurtle(Array.from(aclResource)), { status: 200, url: "https://some.pod/resource.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = getGroupAccessAll(resource, { fetch: mockFetch, }); await expect(result).resolves.toStrictEqual({ "https://some.pod/groups#group": { read: false, append: false, write: false, controlRead: true, controlWrite: true, }, }); }); }); describe("setAgentAccess", () => { it("calls the included fetcher by default", async () => { const mockedFetcher = jest.requireMock("../fetcher.ts") as { fetch: jest.Mock< ReturnType<typeof window.fetch>, [RequestInfo, RequestInit?] >; }; const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); await setAgentResourceAccess(resource, "https://some.pod/profile#agent", { read: true, append: undefined, write: undefined, controlRead: undefined, controlWrite: undefined, }); expect(mockedFetcher.fetch.mock.calls[0][0]).toEqual( "https://some.pod/resource.acl" ); }); it("returns null if no ACL is accessible", async () => { const mockFetch = jest .fn(window.fetch) // No resource ACL available... .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/resource.acl", }) ) // Link to the fallback ACL... .mockResolvedValueOnce( mockResponse("", { status: 200, url: "https://some.pod/", headers: { Link: '<.acl>; rel="acl"', }, }) ) // Get the fallback ACL .mockResolvedValueOnce( mockResponse("", { status: 404, url: "https://some.pod/.acl", }) ); const resource = getMockDataset( "https://some.pod/resource", "https://some.pod/resource.acl" ); const result = setAgentResourceAccess( resource, "https://some.pod/profile#agent", { read: true, append: undefined, write: undefined, controlRead: undefined, controlWrite: undefined, }, { fetch: mockFetch, } ); await expect(result).resolves.toBeNull(); }); it("returns null if no ACL is advertised by the target resource", async () => { const mockFetch = jest.fn(window.fetch).mockResolvedValue( mockResponse("ACL not found", { status: 404, url: "htt