Skip to content

Commit

Permalink
Universal API no longer throws on missing ACL (#2200)
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 authored Sep 26, 2023
1 parent 403a084 commit 70c161d
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ The following changes are pending, and will be applied on the next major release

The following changes have been implemented but not released yet:

### Bugfixes

- [1549](https://github.com/inrupt/solid-client-js/issues/1549): The universal API had a bug preventing it from handling correctly resources with a missing ACL. This is now resolved, and it correctly proceeds to creating the target ACL if required.

## [1.30.1] - 2023-09-15

### Patch
Expand Down
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;
}

1 comment on commit 70c161d

@vercel
Copy link

@vercel vercel bot commented on 70c161d Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

solid-client-js – ./

solid-client.vercel.app
solid-client-js-git-main-inrupt.vercel.app
solid-client-js-inrupt.vercel.app

Please sign in to comment.