From b4ead82cd1e1a97834504637d681be7e171e14de Mon Sep 17 00:00:00 2001 From: Aaron Coburn Date: Mon, 11 Nov 2024 12:10:29 -0600 Subject: [PATCH] Add tests and javadocs --- .../client/accessgrant/AccessGrantClient.java | 50 ++-- .../client/accessgrant/CredentialFilter.java | 235 +++++++++++------- .../client/accessgrant/CredentialResult.java | 45 +++- .../accessgrant/CredentialFilterTest.java | 164 ++++++++++++ 4 files changed, 372 insertions(+), 122 deletions(-) create mode 100644 access-grant/src/test/java/com/inrupt/client/accessgrant/CredentialFilterTest.java diff --git a/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java b/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java index 11b68e1a93..821584bf41 100644 --- a/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java +++ b/access-grant/src/main/java/com/inrupt/client/accessgrant/AccessGrantClient.java @@ -357,45 +357,25 @@ public CompletionStage verify(final AccessCredenti */ public CompletionStage> query(final CredentialFilter filter) { final Class clazz = filter.getCredentialType(); - final String type; final Set supportedTypes; if (AccessGrant.class.isAssignableFrom(clazz)) { - type = SOLID_ACCESS_GRANT; supportedTypes = ACCESS_GRANT_TYPES; } else if (AccessRequest.class.isAssignableFrom(clazz)) { - type = SOLID_ACCESS_REQUEST; supportedTypes = ACCESS_REQUEST_TYPES; } else if (AccessDenial.class.isAssignableFrom(clazz)) { - type = SOLID_ACCESS_DENIAL; supportedTypes = ACCESS_DENIAL_TYPES; } else { throw new AccessGrantException("Unsupported type " + clazz + " in query request"); } return v1Metadata().thenCompose(metadata -> { - final List responses = new ArrayList<>(); - // check that query endpoint is nonnull - final URIBuilder query = URIBuilder.newBuilder(metadata.queryEndpoint) - .queryParam("type", type) - .queryParam("pageSize", Integer.toString(filter.getPageSize())); - - filter.getFromAgent().map(URI::toString).ifPresent(a -> query.queryParam("fromAgent", a)); - filter.getToAgent().map(URI::toString).ifPresent(a -> query.queryParam("toAgent", a)); - filter.getStatus().map(CredentialFilter.CredentialStatus::getValue) - .ifPresent(s -> query.queryParam("status", s)); - filter.getResource().map(URI::toString).ifPresent(r -> query.queryParam("resource", r)); - filter.getPurpose().map(URI::toString).ifPresent(p -> query.queryParam("purpose", p)); - filter.getIssuedWithin().map(CredentialFilter.CredentialDuration::name) - .ifPresent(d -> query.queryParam("issuedWithin", d)); - filter.getRevokedWithin().map(CredentialFilter.CredentialDuration::name) - .ifPresent(d -> query.queryParam("revokedWithin", d)); - filter.getPage().ifPresent(p -> query.queryParam("page", p)); - - final Request req = Request.newBuilder(query.build()).GET().build(); + // TODO check that query endpoint is nonnull + final Request req = Request.newBuilder(filter.asURI(metadata.queryEndpoint)).GET().build(); return client.send(req, Response.BodyHandlers.ofInputStream()).thenApply(response -> { try (final InputStream input = response.body()) { if (isSuccess(response.statusCode())) { - final Map links = processFilterResponseHeaders(response.headers()); + final Map> links = processFilterResponseHeaders(response.headers(), + filter); final List items = processFilterResponseBody(input, supportedTypes, clazz); return new CredentialResult<>(items, links.get("first"), links.get("prev"), links.get("next"), links.get("last")); @@ -411,8 +391,9 @@ public CompletionStage> query(f }); } - Map processFilterResponseHeaders(final Headers headers) { - final Map links = new HashMap<>(); + Map> processFilterResponseHeaders(final Headers headers, + final CredentialFilter filter) { + final Map> links = new HashMap<>(); final List linkHeaders = headers.allValues("Link"); if (!linkHeaders.isEmpty()) { Headers.Link.parse(linkHeaders.toArray(linkHeaders.toArray(new String[0]))) @@ -420,13 +401,28 @@ Map processFilterResponseHeaders(final Headers headers) { final String rel = link.getParameter("rel"); final URI uri = link.getUri(); if (rel != null && uri != null) { - links.put(rel, uri); + final String page = getPageQueryParam(uri); + links.put(rel, CredentialFilter.newBuilder(filter).page(page) + .build(filter.getCredentialType())); } }); } return links; } + static String getPageQueryParam(final URI uri) { + final String params = uri.getQuery(); + if (params != null) { + for (final String param : params.split("&")) { + final String parts[] = param.split("=", 2); + if (parts.length == 2 && "page".equals(parts[0])) { + return parts[1]; + } + } + } + return null; + } + @SuppressWarnings("unchecked") List processFilterResponseBody(final InputStream input, final Set validTypes, final Class clazz) throws IOException { diff --git a/access-grant/src/main/java/com/inrupt/client/accessgrant/CredentialFilter.java b/access-grant/src/main/java/com/inrupt/client/accessgrant/CredentialFilter.java index e936a331be..3b5868e6e9 100644 --- a/access-grant/src/main/java/com/inrupt/client/accessgrant/CredentialFilter.java +++ b/access-grant/src/main/java/com/inrupt/client/accessgrant/CredentialFilter.java @@ -20,6 +20,8 @@ */ package com.inrupt.client.accessgrant; +import com.inrupt.client.util.URIBuilder; + import java.net.URI; import java.time.Duration; import java.util.HashMap; @@ -27,13 +29,19 @@ import java.util.Objects; import java.util.Optional; +/** + * A class for representing Access Grant query filters. + * + * @param the credential type + */ public class CredentialFilter { + /** The default size of result pages. */ public static final int DEFAULT_PAGE_SIZE = 20; + /** The maximum result page size. */ public static final int MAX_PAGE_SIZE = 100; - private static final Map STATUS_VALUES = buildStatusValues(); - private static final Map DURATION_VALUES = buildDurationValues(); + private static final Map TYPE_VALUES = buildTypeValues(); private final URI fromAgent; private final URI toAgent; @@ -62,135 +70,149 @@ public class CredentialFilter { this.pageSize = pageSize; } + /** + * A filter for an agent that issued credentials. + * + * @return the agent issuer, if present + */ public Optional getFromAgent() { return Optional.ofNullable(fromAgent); } + /** + * A filter for an agent that is the target of credentials. + * + * @return the target agent, if present + */ public Optional getToAgent() { return Optional.ofNullable(toAgent); } + /** + * A filter for credential status. + * + * @return the credential status, if present + */ public Optional getStatus() { return Optional.ofNullable(status); } + /** + * A filter for a target resource. + * + * @return the credential target resource, if present + */ public Optional getResource() { return Optional.ofNullable(resource); } + /** + * A filter for a target purpose. + * + * @return the credential target purpose, if present + */ public Optional getPurpose() { return Optional.ofNullable(purpose); } + /** + * A filter for credential issuance. + * + * @return the credential issuance filter, if present + */ public Optional getIssuedWithin() { return Optional.ofNullable(issuedWithin); } + /** + * A filter for credential revocation. + * + * @return the credential revocation filter, if present + */ public Optional getRevokedWithin() { return Optional.ofNullable(revokedWithin); } + /** + * A filter for a result page. + * + * @return the page indicator, if present + */ public Optional getPage() { return Optional.ofNullable(page); } + /** + * The page size for result sets. + * + * @return the result set size + */ public int getPageSize() { return pageSize; } + /** + * Convert the filter to a URI. + * + * @param baseUrl the base URL for the filter + * @return the formatted URI + */ + public URI asURI(final URI baseUrl) { + final URIBuilder builder = URIBuilder.newBuilder(baseUrl) + .queryParam("type", TYPE_VALUES.get(getCredentialType().getSimpleName())) + .queryParam("pageSize", Integer.toString(getPageSize())); + + getPurpose().map(URI::toString).ifPresent(p -> builder.queryParam("purpose", p)); + getResource().map(URI::toString).ifPresent(r -> builder.queryParam("resource", r)); + getFromAgent().map(URI::toString).ifPresent(a -> builder.queryParam("fromAgent", a)); + getToAgent().map(URI::toString).ifPresent(a -> builder.queryParam("toAgent", a)); + + getStatus().map(CredentialStatus::getValue).ifPresent(s -> builder.queryParam("status", s)); + getIssuedWithin().map(CredentialDuration::name).ifPresent(d -> builder.queryParam("issuedWithin", d)); + getRevokedWithin().map(CredentialDuration::name).ifPresent(d -> builder.queryParam("revokedWithin", d)); + + getPage().ifPresent(p -> builder.queryParam("page", p)); + + return builder.build(); + } + /* package private */ Class getCredentialType() { return clazz; } + /** + * Create a new CredentialFilter builder. + * + * @return the builder + */ public static Builder newBuilder() { return new Builder(); } - private static Map buildStatusValues() { - final Map values = new HashMap<>(); - for (final CredentialStatus status : CredentialStatus.values()) { - values.put(status.getValue(), status); - } - return values; - } - - private static Map buildDurationValues() { - final Map values = new HashMap<>(); - for (final CredentialDuration duration : CredentialDuration.values()) { - values.put(duration.name(), duration); - } - return values; - } - /** - * Create a credential filter from a URI. + * Create a new CredentialFilter builder from an existing filter. * * @param the credential type - * @param uri the provided URI - * @return the filter + * @param filter an existing credential filter + * @return the builder */ - public static CredentialFilter of(final URI uri) { - final Map params = new HashMap<>(); - final String query = uri.getQuery(); - if (query != null) { - for (final String q : query.split("&")) { - final String[] parts = q.split("=", 2); - if (parts.length == 2) { - params.put(parts[0], parts[1]); - } - } - } - - if (!params.isEmpty()) { - final Builder builder = newBuilder(); - if (params.containsKey("purpose")) { - builder.purpose(URI.create(params.get("purpose"))); - } - if (params.containsKey("resource")) { - builder.resource(URI.create(params.get("resource"))); - } - if (params.containsKey("fromAgent")) { - builder.fromAgent(URI.create(params.get("fromAgent"))); - } - if (params.containsKey("toAgent")) { - builder.toAgent(URI.create(params.get("toAgent"))); - } - if (params.containsKey("status") && STATUS_VALUES.containsKey(params.get("status"))) { - builder.status(STATUS_VALUES.get(params.get("status"))); - } - if (params.containsKey("issuedWithin") && DURATION_VALUES.containsKey(params.get("issuedWithin"))) { - builder.issuedWithin(DURATION_VALUES.get(params.get("issuedWithin"))); - } - if (params.containsKey("revokedWithin") && DURATION_VALUES.containsKey(params.get("revokedWithin"))) { - builder.revokedWithin(DURATION_VALUES.get(params.get("revokedWithin"))); - } - if (params.containsKey("page")) { - builder.page(params.get("page")); - } - if (params.containsKey("pageSize")) { - builder.pageSize(Integer.parseInt(params.get("pageSize"))); - } - if (params.containsKey("type")) { - final String type = params.get("type"); - if ("SolidAccessRequest".equals(type)) { - @SuppressWarnings("unchecked") - final CredentialFilter value = (CredentialFilter) builder.build(AccessRequest.class); - return value; - } else if ("SolidAccessGrant".equals(type)) { - @SuppressWarnings("unchecked") - final CredentialFilter value = (CredentialFilter) builder.build(AccessGrant.class); - return value; - } else if ("SolidAccessDenial".equals(type)) { - @SuppressWarnings("unchecked") - final CredentialFilter value = (CredentialFilter) builder.build(AccessDenial.class); - return value; - } - } - } - throw new AccessGrantException("Cannot build a filter from the provided URI"); + public static Builder newBuilder(final CredentialFilter filter) { + final Builder builder = new Builder().pageSize(filter.getPageSize()); + filter.getPurpose().ifPresent(builder::purpose); + filter.getResource().ifPresent(builder::resource); + filter.getFromAgent().ifPresent(builder::fromAgent); + filter.getToAgent().ifPresent(builder::toAgent); + filter.getStatus().ifPresent(builder::status); + filter.getIssuedWithin().ifPresent(builder::issuedWithin); + filter.getRevokedWithin().ifPresent(builder::revokedWithin); + filter.getPage().ifPresent(builder::page); + return builder; } + /** + * The CredentialFilter builder. + */ public static class Builder { private URI builderPurpose; private URI builderResource; @@ -202,11 +224,15 @@ public static class Builder { private String builderPage; private int builderPageSize = DEFAULT_PAGE_SIZE; + /* Package private */ + Builder() { + } + /** * Add a purpose filter. * * @param purpose the purpose identifier - * @return the builder + * @return this builder */ public Builder purpose(final URI purpose) { builderPurpose = purpose; @@ -217,7 +243,7 @@ public Builder purpose(final URI purpose) { * Add a resource filter. * * @param resource the resource identifier - * @return the builder + * @return this builder */ public Builder resource(final URI resource) { builderResource = resource; @@ -228,7 +254,7 @@ public Builder resource(final URI resource) { * Add a fromAgent filter. * * @param fromAgent the agent identifier - * @return the builder + * @return this builder */ public Builder fromAgent(final URI fromAgent) { builderFromAgent = fromAgent; @@ -239,7 +265,7 @@ public Builder fromAgent(final URI fromAgent) { * Add a toAgent filter. * * @param toAgent the agent identifier - * @return the builder + * @return this builder */ public Builder toAgent(final URI toAgent) { builderToAgent = toAgent; @@ -250,28 +276,52 @@ public Builder toAgent(final URI toAgent) { * Add a status filter. * * @param status the status value - * @return the builder + * @return this builder */ public Builder status(final CredentialStatus status) { builderStatus = status; return this; } + /** + * Add an issuance date filter. + * + * @param issuedWithin the issuance date filter + * @return this builder + */ public Builder issuedWithin(final CredentialDuration issuedWithin) { builderIssuedWithin = issuedWithin; return this; } + /** + * Add a revocation date filter. + * + * @param revokedWithin the revocation date filter + * @return this builder + */ public Builder revokedWithin(final CredentialDuration revokedWithin) { builderRevokedWithin = revokedWithin; return this; } + /** + * Add a page indicator. + * + * @param page the page indicator + * @return this builder + */ public Builder page(final String page) { builderPage = page; return this; } + /** + * Indicate the size of a page. + * + * @param pageSize the size of a page + * @return this builder + */ public Builder pageSize(final int pageSize) { if (pageSize > 0 && pageSize < MAX_PAGE_SIZE) { builderPageSize = pageSize; @@ -281,6 +331,13 @@ public Builder pageSize(final int pageSize) { return this; } + /** + * Build a credential filter. + * + * @param the credential type + * @param clazz the credential type + * @return the credential filter + */ public CredentialFilter build(final Class clazz) { return new CredentialFilter<>(builderFromAgent, builderToAgent, builderStatus, builderResource, builderPurpose, builderIssuedWithin, builderRevokedWithin, builderPageSize, builderPage, clazz); @@ -352,4 +409,12 @@ public String getValue() { return value; } } + + private static Map buildTypeValues() { + final Map values = new HashMap<>(); + values.put("AccessGrant", "SolidAccessGrant"); + values.put("AccessDenial", "SolidAccessDenial"); + values.put("AccessRequest", "SolidAccessRequest"); + return values; + } } diff --git a/access-grant/src/main/java/com/inrupt/client/accessgrant/CredentialResult.java b/access-grant/src/main/java/com/inrupt/client/accessgrant/CredentialResult.java index 2a449d9549..5da8657732 100644 --- a/access-grant/src/main/java/com/inrupt/client/accessgrant/CredentialResult.java +++ b/access-grant/src/main/java/com/inrupt/client/accessgrant/CredentialResult.java @@ -20,7 +20,6 @@ */ package com.inrupt.client.accessgrant; -import java.net.URI; import java.util.List; import java.util.Optional; @@ -32,13 +31,14 @@ public class CredentialResult { private final List items; - private final URI first; - private final URI last; - private final URI prev; - private final URI next; + private final CredentialFilter first; + private final CredentialFilter last; + private final CredentialFilter prev; + private final CredentialFilter next; /* package private */ - CredentialResult(final List items, final URI first, final URI prev, final URI next, final URI last) { + CredentialResult(final List items, final CredentialFilter first, final CredentialFilter prev, + final CredentialFilter next, final CredentialFilter last) { this.items = items; this.first = first; this.prev = prev; @@ -46,22 +46,47 @@ public class CredentialResult { this.last = last; } - public Optional firstPage() { + /** + * Get a filter for the first page of results. + * + * @return the first page filter, if present + */ + public Optional> firstPage() { return Optional.ofNullable(first); } - public Optional prevPage() { + /** + * Get a filter for the previous page of results. + * + * @return the previous page filter, if present + */ + public Optional> prevPage() { return Optional.ofNullable(prev); } - public Optional nextPage() { + /** + * Get a filter for the next page of results. + * + * @return the next page filter, if present + */ + public Optional> nextPage() { return Optional.ofNullable(next); } - public Optional lastPage() { + /** + * Get a filter for the last page of results. + * + * @return the last page filter, if present + */ + public Optional> lastPage() { return Optional.ofNullable(last); } + /** + * Get the credential items in the result page. + * + * @return the credential items + */ public List getItems() { return items; } diff --git a/access-grant/src/test/java/com/inrupt/client/accessgrant/CredentialFilterTest.java b/access-grant/src/test/java/com/inrupt/client/accessgrant/CredentialFilterTest.java new file mode 100644 index 0000000000..a8bfbf1f88 --- /dev/null +++ b/access-grant/src/test/java/com/inrupt/client/accessgrant/CredentialFilterTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 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. + */ +package com.inrupt.client.accessgrant; + +import static org.junit.jupiter.api.Assertions.*; + +import com.inrupt.client.util.URIBuilder; + +import java.net.URI; +import java.time.Duration; +import java.util.Optional; +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +class CredentialFilterTest { + + private static final URI ALICE = URI.create("https://agent.test/alice"); + private static final URI BOB = URI.create("https://agent.test/bob"); + private static final URI PURPOSE = URI.create("https://purpose.test/Purpose"); + private static final URI RESOURCE = URI.create("https://storage.test/data/"); + private static final URI BASE_URL = URI.create("https://credential.test/query"); + + @Test + void testCredentialEmptyFilterBuilder() { + final CredentialFilter filter = CredentialFilter.newBuilder().build(AccessGrant.class); + assertFalse(filter.getPage().isPresent()); + assertFalse(filter.getFromAgent().isPresent()); + assertFalse(filter.getToAgent().isPresent()); + assertFalse(filter.getPurpose().isPresent()); + assertFalse(filter.getResource().isPresent()); + assertFalse(filter.getStatus().isPresent()); + assertFalse(filter.getIssuedWithin().isPresent()); + assertFalse(filter.getRevokedWithin().isPresent()); + assertEquals(20, filter.getPageSize()); + assertEquals(AccessGrant.class, filter.getCredentialType()); + + final URI expectedUri = URIBuilder.newBuilder(BASE_URL) + .queryParam("type", "SolidAccessGrant") + .queryParam("pageSize", "20").build(); + assertEquals(expectedUri, filter.asURI(BASE_URL)); + } + + @Test + void testCredentialFilterBuilder() { + final String page = UUID.randomUUID().toString(); + final CredentialFilter.CredentialStatus status = CredentialFilter.CredentialStatus.PENDING; + final CredentialFilter.CredentialDuration issuedWithin = CredentialFilter.CredentialDuration.P1D; + final CredentialFilter.CredentialDuration revokedWithin = CredentialFilter.CredentialDuration.P7D; + + final CredentialFilter filter = CredentialFilter.newBuilder() + .status(status) + .fromAgent(ALICE) + .toAgent(BOB) + .purpose(PURPOSE) + .resource(RESOURCE) + .issuedWithin(issuedWithin) + .revokedWithin(revokedWithin) + .page(page) + .pageSize(40) + .build(AccessRequest.class); + + assertEquals(Optional.of(ALICE), filter.getFromAgent()); + assertEquals(Optional.of(BOB), filter.getToAgent()); + assertEquals(Optional.of(PURPOSE), filter.getPurpose()); + assertEquals(Optional.of(RESOURCE), filter.getResource()); + assertEquals(Optional.of(status), filter.getStatus()); + assertEquals(Optional.of(issuedWithin), filter.getIssuedWithin()); + assertEquals(Optional.of(revokedWithin), filter.getRevokedWithin()); + assertEquals(Optional.of(page), filter.getPage()); + assertEquals(40, filter.getPageSize()); + assertEquals(AccessRequest.class, filter.getCredentialType()); + + final URI expectedUri = URIBuilder.newBuilder(BASE_URL) + .queryParam("type", "SolidAccessRequest") + .queryParam("pageSize", "40") + .queryParam("purpose", PURPOSE.toString()) + .queryParam("resource", RESOURCE.toString()) + .queryParam("fromAgent", ALICE.toString()) + .queryParam("toAgent", BOB.toString()) + .queryParam("status", status.getValue()) + .queryParam("issuedWithin", issuedWithin.name()) + .queryParam("revokedWithin", revokedWithin.name()) + .queryParam("page", page) + .build(); + assertEquals(expectedUri, filter.asURI(BASE_URL)); + } + + @Test + void testCredentialFilterAmendedBuilder() { + final String page = UUID.randomUUID().toString(); + final CredentialFilter.CredentialStatus status = CredentialFilter.CredentialStatus.PENDING; + final CredentialFilter.CredentialDuration issuedWithin = CredentialFilter.CredentialDuration.P1D; + final CredentialFilter.CredentialDuration revokedWithin = CredentialFilter.CredentialDuration.P7D; + + final CredentialFilter filter1 = CredentialFilter.newBuilder() + .status(status) + .fromAgent(BOB) + .toAgent(ALICE) + .purpose(PURPOSE) + .resource(RESOURCE) + .issuedWithin(issuedWithin) + .revokedWithin(revokedWithin) + .page(page) + .pageSize(10) + .build(AccessRequest.class); + + final String page2 = UUID.randomUUID().toString(); + final CredentialFilter filter2 = CredentialFilter.newBuilder(filter1) + .page(page2) + .build(AccessRequest.class); + + assertEquals(Optional.of(BOB), filter2.getFromAgent()); + assertEquals(Optional.of(ALICE), filter2.getToAgent()); + assertEquals(Optional.of(PURPOSE), filter2.getPurpose()); + assertEquals(Optional.of(RESOURCE), filter2.getResource()); + assertEquals(Optional.of(status), filter2.getStatus()); + assertEquals(Optional.of(issuedWithin), filter2.getIssuedWithin()); + assertEquals(Optional.of(revokedWithin), filter2.getRevokedWithin()); + assertEquals(Optional.of(page2), filter2.getPage()); + assertEquals(10, filter2.getPageSize()); + assertEquals(AccessRequest.class, filter2.getCredentialType()); + + final URI expectedUri = URIBuilder.newBuilder(BASE_URL) + .queryParam("type", "SolidAccessRequest") + .queryParam("pageSize", "10") + .queryParam("purpose", PURPOSE.toString()) + .queryParam("resource", RESOURCE.toString()) + .queryParam("fromAgent", BOB.toString()) + .queryParam("toAgent", ALICE.toString()) + .queryParam("status", status.getValue()) + .queryParam("issuedWithin", issuedWithin.name()) + .queryParam("revokedWithin", revokedWithin.name()) + .queryParam("page", page2) + .build(); + assertEquals(expectedUri, filter2.asURI(BASE_URL)); + } + + @Test + void testDurations() { + assertEquals(Duration.ofDays(1), CredentialFilter.CredentialDuration.P1D.asDuration()); + assertEquals(Duration.ofDays(7), CredentialFilter.CredentialDuration.P7D.asDuration()); + assertEquals(Duration.ofDays(30), CredentialFilter.CredentialDuration.P1M.asDuration()); + assertEquals(Duration.ofDays(90), CredentialFilter.CredentialDuration.P3M.asDuration()); + } +}