Skip to content

Commit

Permalink
Universal API no longer throws on missing ACL
Browse files Browse the repository at this point in the history
Fixes #1549

A resource having an ACL link, but this link not resolving to an ACL is
a legitimate situation in the ACL protocol. The universal API had a bug
that was not allowing the underlying ACL API to create the target ACL if
it was missing. It is now supported.
  • Loading branch information
NSeydoux committed Sep 26, 2023
1 parent 403a084 commit de08c32
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 18 deletions.
73 changes: 58 additions & 15 deletions src/universal/getAclServerResourceInfo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,78 @@
//

import { jest, describe, it, expect } from "@jest/globals";
import { getResourceInfo } from "../resource/resource";
import * as ResourceModule from "../resource/resource";
import { getAclServerResourceInfo } from "./getAclServerResourceInfo";
import type { WithServerResourceInfo } from "../interfaces";

const { getResourceInfo } = ResourceModule;

jest.mock("../resource/resource", () => ({
getResourceInfo: jest.fn().mockImplementation(() => ({})),
getResourceInfo: jest.fn(),
}));

const mockResourceInfo = (options: {
aclUrl?: string;
}): WithServerResourceInfo["internal_resourceInfo"] => ({
isRawData: false,
aclUrl: options.aclUrl,
sourceIri: "https://example.org/some-resource",
linkedResources: {},
});

describe("getAclServerResourceInfo", () => {
it("fetches the ACL resource info if the resource has an ACL", async () => {
await getAclServerResourceInfo({
internal_resourceInfo: { aclUrl: "x" },
} as any);
expect(getResourceInfo).toHaveBeenCalledTimes(1);
expect(getResourceInfo).toHaveBeenCalledWith("x", undefined);
const aclResourceInfo = mockResourceInfo({});
const { getResourceInfo } = jest.mocked(ResourceModule);
getResourceInfo.mockResolvedValueOnce({
internal_resourceInfo: aclResourceInfo,
});

await expect(
getAclServerResourceInfo({
internal_resourceInfo: mockResourceInfo({
aclUrl: "https://example.org/some-acl",
}),
}),
).resolves.toStrictEqual({
internal_resourceInfo: aclResourceInfo,
});
});

it("returns null if the resource ACL cannot be discovered", async () => {
const { getResourceInfo } = jest.mocked(ResourceModule);
getResourceInfo.mockRejectedValueOnce(null);
await expect(
getAclServerResourceInfo({
internal_resourceInfo: mockResourceInfo({
aclUrl: "https://example.org/some-missing-acl",
}),
}),
).resolves.toBeNull();
});

it("returns null if the resource has no ACL", async () => {
await getAclServerResourceInfo({
internal_resourceInfo: { aclUrl: undefined },
} as any);
expect(getResourceInfo).toHaveBeenCalledTimes(0);
it("returns null if fetching the resource ACL fails", async () => {
await expect(
getAclServerResourceInfo({
internal_resourceInfo: mockResourceInfo({ aclUrl: undefined }),
}),
).resolves.toBeNull();
});

it("passes the fetch option to fetch the ACL resource info", async () => {
const mockedFetch = jest.fn<typeof fetch>();
await getAclServerResourceInfo(
{ internal_resourceInfo: { aclUrl: "x" } } as any,
{ fetch: "x" } as any,
{
internal_resourceInfo: mockResourceInfo({
aclUrl: "https://example.org/some-acl",
}),
},
{ fetch: mockedFetch },
);
expect(getResourceInfo).toHaveBeenCalledTimes(1);
expect(getResourceInfo).toHaveBeenCalledWith("x", { fetch: "x" });
expect(getResourceInfo).toHaveBeenCalledWith(
"https://example.org/some-acl",
{ fetch: mockedFetch },
);
});
});
16 changes: 13 additions & 3 deletions src/universal/getAclServerResourceInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,18 @@ export async function getAclServerResourceInfo(
resource: WithServerResourceInfo,
options?: DefaultOptions,
): Promise<WithServerResourceInfo | null> {
if (typeof resource.internal_resourceInfo.aclUrl === "string") {
return getResourceInfo(resource.internal_resourceInfo.aclUrl, options);
if (typeof resource.internal_resourceInfo.aclUrl !== "string") {
return null;
}
try {
return await getResourceInfo(
resource.internal_resourceInfo.aclUrl,
options,
);
} catch {
// A WAC-governed resource may have a link to a non-existant ACL (by design).
// The absence of an ACL at the target URL is a useful information that is
// used by the universal API to pick between ACR and WAC.
return null;
}
return null;
}

0 comments on commit de08c32

Please sign in to comment.