Skip to content

Commit

Permalink
Align wrapper signatures with underlying function (#1100)
Browse files Browse the repository at this point in the history
This prevents misalignment between the `/resource` module functions signatures and the underlying `@inrupt/solid-client` functions they wrap around. The `/resource` module directly reuses the signatures of the wrapped function instead of redefining its own, which updates the signature on dependency upgrade.
---------

Co-authored-by: Pete Edwards <[email protected]>
  • Loading branch information
NSeydoux and edwardsph authored Aug 27, 2024
1 parent 1688951 commit 567eb09
Show file tree
Hide file tree
Showing 18 changed files with 188 additions and 274 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ The following changes are pending, and will be applied on the next major release

- Node 22 is now supported

### Bugfixes

- The `/resources` module function have their signature now aligned with the underlying `@inrupt/solid-client` functions.
Namely, the `options` parameter for `saveSolidDatasetAt` and `getSolidDataset` support additional entries that were
already available in `@inrupt/solid-client`.

## [3.0.5](https://github.com/inrupt/solid-client-access-grants-js/releases/tag/v3.0.5) - 2024-07-31

### Bugfixes
Expand Down
43 changes: 29 additions & 14 deletions src/common/verify/isValidAccessGrant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,24 @@ import { jest, describe, it, expect, beforeAll } from "@jest/globals";
import {
isVerifiableCredential,
getVerifiableCredentialApiConfiguration,
verifiableCredentialToDataset,
} from "@inrupt/solid-client-vc";

import type * as SolidClient from "@inrupt/solid-client";
import type * as VcLibrary from "@inrupt/solid-client-vc";
import { isValidAccessGrant } from "./isValidAccessGrant";

jest.mock("@inrupt/solid-client", () => {
const solidClientModule = jest.requireActual("@inrupt/solid-client") as any;
const solidClientModule = jest.requireActual(
"@inrupt/solid-client",
) as typeof SolidClient;
solidClientModule.getSolidDataset = jest.fn(
solidClientModule.getSolidDataset,
);
solidClientModule.getWellKnownSolid = jest.fn();
return solidClientModule;
return {
...solidClientModule,
getWellKnownSolid: jest.fn<(typeof SolidClient)["getWellKnownSolid"]>(),
getSolidDataset: jest.fn<(typeof SolidClient)["getSolidDataset"]>(),
};
});

jest.mock("@inrupt/solid-client-vc", () => {
Expand Down Expand Up @@ -150,9 +156,12 @@ describe("isValidAccessGrant", () => {
}),
);

await isValidAccessGrant(MOCK_ACCESS_GRANT_BASE as any, {
fetch: mockedFetch,
});
await isValidAccessGrant(
await verifiableCredentialToDataset(MOCK_ACCESS_GRANT_BASE),
{
fetch: mockedFetch,
},
);

expect(mockedFetch).toHaveBeenCalledWith(
expect.anything(),
Expand Down Expand Up @@ -290,10 +299,13 @@ describe("isValidAccessGrant", () => {
status: 200,
}),
);
await isValidAccessGrant(MOCK_ACCESS_GRANT_BASE as any, {
fetch: mockedFetch,
verificationEndpoint: "https://some.verification.api",
});
await isValidAccessGrant(
await verifiableCredentialToDataset(MOCK_ACCESS_GRANT_BASE),
{
fetch: mockedFetch,
verificationEndpoint: "https://some.verification.api",
},
);
expect(mockedFetch).toHaveBeenCalledWith(
"https://some.verification.api",
expect.anything(),
Expand Down Expand Up @@ -352,9 +364,12 @@ describe("isValidAccessGrant", () => {
}),
);

await isValidAccessGrant(MOCK_ACCESS_GRANT_BASE as any, {
fetch: mockedFetch,
});
await isValidAccessGrant(
await verifiableCredentialToDataset(MOCK_ACCESS_GRANT_BASE),
{
fetch: mockedFetch,
},
);

expect(mockedDiscovery).toHaveBeenCalledWith(MOCK_ACCESS_GRANT.issuer);
});
Expand Down
144 changes: 68 additions & 76 deletions src/gConsent/discover/getAccessManagementUi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
//

import { jest, describe, it, expect } from "@jest/globals";
import type { getWellKnownSolid, getSolidDataset } from "@inrupt/solid-client";
import { mockSolidDatasetFrom } from "@inrupt/solid-client";
import { getSolidDataset, mockSolidDatasetFrom } from "@inrupt/solid-client";
import type * as SolidClient from "@inrupt/solid-client";

import { getAccessManagementUi } from "./getAccessManagementUi";

Expand All @@ -32,52 +32,45 @@ import {
} from "../request/request.mock";

jest.mock("@inrupt/solid-client", () => {
const solidClientModule = jest.requireActual("@inrupt/solid-client") as any;
solidClientModule.getSolidDataset = jest.fn(
solidClientModule.getSolidDataset,
);
solidClientModule.getWellKnownSolid = jest.fn();
return solidClientModule;
const solidClientModule = jest.requireActual(
"@inrupt/solid-client",
) as typeof SolidClient;
return {
...solidClientModule,
getSolidDataset: jest.fn(),
getWellKnownSolid: jest.fn(),
};
});

describe("getAccessManagementUi", () => {
it("uses the provided fetch if any", async () => {
const spiedGetDataset = jest
.spyOn(
jest.requireMock("@inrupt/solid-client") as {
getSolidDataset: typeof getSolidDataset;
},
"getSolidDataset",
)
.mockResolvedValueOnce(mockWebIdWithUi("https://some.webid"));
const { getSolidDataset: mockedGetDataset } = jest.requireMock(
"@inrupt/solid-client",
) as jest.Mocked<typeof SolidClient>;

mockedGetDataset.mockResolvedValueOnce(
mockWebIdWithUi("https://some.webid"),
);
const mockedFetch = jest.fn<typeof fetch>();

await getAccessManagementUi("https://some.webid", { fetch: mockedFetch });

expect(spiedGetDataset).toHaveBeenCalledWith("https://some.webid", {
expect(mockedGetDataset).toHaveBeenCalledWith("https://some.webid", {
fetch: mockedFetch,
});
});

it("throws if the WebID document cannot be dereferenced", async () => {
const solidClient = jest.requireMock("@inrupt/solid-client") as {
getSolidDataset: typeof getSolidDataset;
getWellKnownSolid: typeof getWellKnownSolid;
};
jest.spyOn(solidClient, "getSolidDataset").mockRejectedValue("Some error");
jest.mocked(getSolidDataset).mockRejectedValue("Some error");

await expect(getAccessManagementUi("https://some.webid")).rejects.toThrow(
/some.webid.*Some error/,
);
});

it("throws if the WebID document does not contain the WebID", async () => {
const solidClient = jest.requireMock("@inrupt/solid-client") as {
getSolidDataset: typeof getSolidDataset;
getWellKnownSolid: typeof getWellKnownSolid;
};
jest
.spyOn(solidClient, "getSolidDataset")
.mocked(getSolidDataset)
.mockResolvedValue(mockSolidDatasetFrom("https://some.webid"));

await expect(getAccessManagementUi("https://some.webid")).rejects.toThrow(
Expand All @@ -86,48 +79,45 @@ describe("getAccessManagementUi", () => {
});

it("returns the IRI advertized by the user's profile when present", async () => {
const solidClient = jest.requireMock("@inrupt/solid-client") as {
getSolidDataset: typeof getSolidDataset;
getWellKnownSolid: typeof getWellKnownSolid;
};
jest
.spyOn(solidClient, "getSolidDataset")
.mockResolvedValueOnce(mockWebIdWithUi("https://some.webid"));
const spiedGetWellKnown = jest.spyOn(solidClient, "getWellKnownSolid");
const {
getSolidDataset: mockedGetSolidDataset,
getWellKnownSolid: mockedGetWellKnownSolid,
} = jest.requireMock("@inrupt/solid-client") as jest.Mocked<
typeof SolidClient
>;
mockedGetSolidDataset.mockResolvedValueOnce(
mockWebIdWithUi("https://some.webid"),
);

await expect(getAccessManagementUi("https://some.webid")).resolves.toBe(
MOCKED_ACCESS_UI_IRI,
);
// If the profile contains a preferred UI, the .well-known document should not be looked up.
expect(spiedGetWellKnown).not.toHaveBeenCalled();
expect(mockedGetWellKnownSolid).not.toHaveBeenCalled();
});

it("falls back to the IRI advertized by the user's Pod provider when present", async () => {
const solidClient = jest.requireMock("@inrupt/solid-client") as {
getSolidDataset: typeof getSolidDataset;
getWellKnownSolid: typeof getWellKnownSolid;
};
jest
.spyOn(solidClient, "getSolidDataset")
.mockResolvedValueOnce(mockWebIdWithUi("https://some.webid", false));
const spiedGetWellKnown = jest
.spyOn(solidClient, "getWellKnownSolid")
.mockResolvedValueOnce(mockWellKnownWithAccess());
const {
getSolidDataset: mockedGetSolidDataset,
getWellKnownSolid: mockedGetWellKnownSolid,
} = jest.requireMock("@inrupt/solid-client") as jest.Mocked<
typeof SolidClient
>;
mockedGetSolidDataset.mockResolvedValueOnce(
mockWebIdWithUi("https://some.webid", false),
);
mockedGetWellKnownSolid.mockResolvedValueOnce(mockWellKnownWithAccess());

await expect(getAccessManagementUi("https://some.webid")).resolves.toBe(
MOCKED_ACCESS_UI_IRI,
);

expect(spiedGetWellKnown).toHaveBeenCalled();
expect(mockedGetWellKnownSolid).toHaveBeenCalled();
});

it("returns undefined if the user's WebID does not link to a storage", async () => {
const solidClient = jest.requireMock("@inrupt/solid-client") as {
getSolidDataset: typeof getSolidDataset;
getWellKnownSolid: typeof getWellKnownSolid;
};
jest
.spyOn(solidClient, "getSolidDataset")
.mocked(getSolidDataset)
.mockResolvedValueOnce(
mockWebIdWithUi("https://some.webid", false, false),
);
Expand All @@ -138,42 +128,44 @@ describe("getAccessManagementUi", () => {
});

it("returns undefined if the host's well-known does not link to a recommended access management UI", async () => {
const solidClient = jest.requireMock("@inrupt/solid-client") as {
getSolidDataset: typeof getSolidDataset;
getWellKnownSolid: typeof getWellKnownSolid;
};
jest
.spyOn(solidClient, "getSolidDataset")
.mockResolvedValueOnce(mockWebIdWithUi("https://some.webid", false));
const spiedGetWellKnown = jest
.spyOn(solidClient, "getWellKnownSolid")
.mockResolvedValueOnce(mockWellKnownWithAccess(false));
const {
getSolidDataset: mockedGetSolidDataset,
getWellKnownSolid: mockedGetWellKnownSolid,
} = jest.requireMock("@inrupt/solid-client") as jest.Mocked<
typeof SolidClient
>;
mockedGetSolidDataset.mockResolvedValueOnce(
mockWebIdWithUi("https://some.webid", false),
);
mockedGetWellKnownSolid.mockResolvedValueOnce(
mockWellKnownWithAccess(false),
);

await expect(
getAccessManagementUi("https://some.webid"),
).resolves.toBeUndefined();

expect(spiedGetWellKnown).toHaveBeenCalled();
expect(mockedGetWellKnownSolid).toHaveBeenCalled();
});

it("returns undefined if the host's well-known is empty", async () => {
const solidClient = jest.requireMock("@inrupt/solid-client") as {
getSolidDataset: typeof getSolidDataset;
getWellKnownSolid: typeof getWellKnownSolid;
};
jest
.spyOn(solidClient, "getSolidDataset")
.mockResolvedValueOnce(mockWebIdWithUi("https://some.webid", false));
const spiedGetWellKnown = jest
.spyOn(solidClient, "getWellKnownSolid")
.mockResolvedValueOnce(
mockSolidDatasetFrom("https://some.server/.well-known/solid"),
);
const {
getSolidDataset: mockedGetSolidDataset,
getWellKnownSolid: mockedGetWellKnownSolid,
} = jest.requireMock("@inrupt/solid-client") as jest.Mocked<
typeof SolidClient
>;
mockedGetSolidDataset.mockResolvedValueOnce(
mockWebIdWithUi("https://some.webid", false),
);
mockedGetWellKnownSolid.mockResolvedValueOnce(
mockSolidDatasetFrom("https://some.server/.well-known/solid"),
);

await expect(
getAccessManagementUi("https://some.webid"),
).resolves.toBeUndefined();

expect(spiedGetWellKnown).toHaveBeenCalled();
expect(mockedGetWellKnownSolid).toHaveBeenCalled();
});
});
18 changes: 9 additions & 9 deletions src/gConsent/manage/denyAccessRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe("denyAccessRequest", () => {

const denail = await denyAccessRequest(accessRequestVc, {
accessEndpoint: "https://some.access-endpoint.override/",
fetch: jest.fn<typeof fetch>() as typeof fetch,
fetch: jest.fn<typeof fetch>(),
returnLegacyJsonld,
});

Expand Down Expand Up @@ -178,7 +178,7 @@ describe("denyAccessRequest", () => {
JSON.parse(JSON.stringify(accessRequestVc)),
{
accessEndpoint: "https://some.access-endpoint.override/",
fetch: jest.fn<typeof fetch>() as typeof fetch,
fetch: jest.fn<typeof fetch>(),
returnLegacyJsonld,
},
);
Expand Down Expand Up @@ -208,7 +208,7 @@ describe("denyAccessRequest", () => {

const legacyDenial = await denyAccessRequest(accessRequestVc, {
accessEndpoint: "https://some.access-endpoint.override/",
fetch: jest.fn<typeof fetch>() as typeof fetch,
fetch: jest.fn<typeof fetch>(),
returnLegacyJsonld: true,
});

Expand All @@ -223,7 +223,7 @@ describe("denyAccessRequest", () => {

const denial = await denyAccessRequest(accessRequestVc, {
accessEndpoint: "https://some.access-endpoint.override/",
fetch: jest.fn<typeof fetch>() as typeof fetch,
fetch: jest.fn<typeof fetch>(),
returnLegacyJsonld: false,
});

Expand Down Expand Up @@ -285,7 +285,7 @@ describe("denyAccessRequest", () => {
purpose: ["https://example.org/some-purpose"],
});
await denyAccessRequest(accessRequestWithPurpose, {
fetch: jest.fn<typeof fetch>() as typeof fetch,
fetch: jest.fn<typeof fetch>(),
returnLegacyJsonld,
});

Expand Down Expand Up @@ -325,12 +325,12 @@ describe("denyAccessRequest", () => {
.spyOn(mockedVcModule, "issueVerifiableCredential")
.mockResolvedValueOnce(await mockAccessGrantVc());
const mockedFetch = jest
.fn(global.fetch)
.fn<typeof fetch>()
.mockResolvedValueOnce(
new Response(JSON.stringify(await mockAccessRequestVc())),
);
await denyAccessRequest("https://some.credential", {
fetch: mockedFetch as typeof fetch,
fetch: mockedFetch,
returnLegacyJsonld,
});

Expand Down Expand Up @@ -365,12 +365,12 @@ describe("denyAccessRequest", () => {
.mockResolvedValueOnce(await mockAccessGrantVc());

const mockedFetch = jest
.fn(global.fetch)
.fn<typeof fetch>()
.mockResolvedValueOnce(
new Response(JSON.stringify(await mockAccessRequestVc())),
);
await denyAccessRequest(new URL("https://some.credential"), {
fetch: mockedFetch as typeof fetch,
fetch: mockedFetch,
returnLegacyJsonld,
});

Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export type { AccessGrantAny } from "./type/AccessGrant";
export type { AccessModes } from "./type/AccessModes";
export type { FetchOptions } from "./type/FetchOptions";
export type { RedirectOptions } from "./type/RedirectOptions";
export type { SaveInContainerOptions } from "./type/SaveInContainerOptions";

export { CredentialIsAccessGrantAny } from "./type/AccessGrant";

Expand Down
Loading

0 comments on commit 567eb09

Please sign in to comment.