Skip to content

Commit

Permalink
Add iteroator unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
NSeydoux committed Dec 20, 2024
1 parent 9150986 commit 55d86e5
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 5 deletions.
75 changes: 73 additions & 2 deletions src/gConsent/query/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
//

import { jest, it, describe, expect } from "@jest/globals";
import type { CredentialFilter } from "./query";
import { DURATION, query } from "./query";
import type { CredentialFilter, CredentialResult } from "./query";
import { DURATION, paginatedQuery, query } from "./query";
import { mockAccessGrantVc } from "../util/access.mock";

describe("query", () => {
Expand Down Expand Up @@ -205,3 +205,74 @@ describe("query", () => {
).rejects.toThrow();
});
});

describe("paginatedQuery", () => {
it("follows the pagination links", async () => {
const nextQueryParams = {
type: "SolidAccessGrant",
page: "6af7d740",
status: "Active",
};
const linkNext = `<https://vc.example.org/query?${new URLSearchParams(nextQueryParams)}>; rel="next"`;
const paginationHeaders = new Headers();
paginationHeaders.append("Link", linkNext);
const mockedGrant = await mockAccessGrantVc();
const pages: CredentialResult[] = [];
const mockedFetch = jest
.fn<typeof fetch>()
.mockResolvedValueOnce(
new Response(
JSON.stringify({
items: [mockedGrant],
}),
{
headers: paginationHeaders,
},
),
)
.mockResolvedValueOnce(
new Response(
JSON.stringify({
items: [mockedGrant],
}),
// The second response has no pagination headers to complete iteration.
),
);

for await (const page of paginatedQuery(
{},
{
queryEndpoint: new URL("https://vc.example.org/query"),
fetch: mockedFetch,
},
)) {
expect(page.items).toHaveLength(1);
pages.push(page);
}
expect(pages).toHaveLength(2);
});

it("supports results not having a next page", async () => {
const mockedGrant = await mockAccessGrantVc();
const pages: CredentialResult[] = [];
const mockedFetch = jest.fn<typeof fetch>().mockResolvedValueOnce(
new Response(
JSON.stringify({
items: [mockedGrant],
}),
),
);

for await (const page of paginatedQuery(
{},
{
queryEndpoint: new URL("https://vc.example.org/query"),
fetch: mockedFetch,
},
)) {
expect(page.items).toHaveLength(1);
pages.push(page);
}
expect(pages).toHaveLength(1);
});
});
30 changes: 27 additions & 3 deletions src/gConsent/query/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ function toQueryUrl(endpoint: URL, filter: CredentialFilter): URL {
}

/**
* Query for Access Credentials (Access Requests, Access Grants or Access Denials) based on a given filter.
* Query for Access Credential (Access Requests, Access Grants or Access Denials) based on a given filter,
* and get a page of results.
*
* @param filter The query filter
* @param options Query options
Expand Down Expand Up @@ -266,7 +267,30 @@ export async function query(
return toCredentialResult(response);
}

export async function* paginateQuery(
/**
* Query for Access Credential (Access Requests, Access Grants or Access Denials) based on a given filter,
* and traverses all of the result pages.
*
* @param filter The query filter
* @param options Query options
* @returns an async iterator going through the result pages
* @since unreleased
*
* @example
* ```
* const pages = paginatedQuery(
* {},
* {
* fetch: session.fetch,
* queryEndpoint: new URL("https://vc.example.org/query"),
* },
* );
* for await (const page of pages) {
* // do something with the result page.
* }
* ```
*/
export async function* paginatedQuery(
filter: CredentialFilter,
options: {
fetch: typeof fetch;
Expand All @@ -282,5 +306,5 @@ export async function* paginateQuery(
page = await query(page.next, options);
}
// Return the last page.
return page;
yield page;
}

0 comments on commit 55d86e5

Please sign in to comment.