From f17ff6cf9a76e127e3daa07c90b9d15431830963 Mon Sep 17 00:00:00 2001 From: Eric T Date: Thu, 21 Aug 2025 04:07:11 +0100 Subject: [PATCH 1/2] Improve REST request and operation coverage --- .../phoenixd/common/rest/OperationTest.java | 1 + .../common/rest/RequestParamTest.java | 2 + .../phoenixd/common/rest/RequestTest.java | 1 + .../common/rest/util/ConfigurationTest.java | 3 + .../common/rest/util/ConstantsTest.java | 1 + .../model/param/CreateInvoiceParamTest.java | 3 + .../model/param/DecodeInvoiceParamTest.java | 1 + .../param/PayBolt11InvoiceParamTest.java | 1 + .../param/PayLightningAddressParamTest.java | 2 + .../response/CreateInvoiceResponseTest.java | 1 + .../response/DecodeInvoiceResponseTest.java | 1 + .../GetLightningAddressResponseTest.java | 1 + .../PayBolt11InvoiceInvoiceResponseTest.java | 1 + .../response/PayInvoiceResponseTest.java | 1 + ...ayLightningAddressInvoiceResponseTest.java | 1 + .../operation/AbstractOperationTest.java | 35 ++++ .../phoenixd/operation/OperationsTest.java | 153 ++++++++++++++++++ .../phoenixd/request/AbstractRequestTest.java | 96 +++++++++++ .../phoenixd/request/impl/RequestTest.java | 122 ++++++++++++++ .../impl/rest/PayRequestFactoryTest.java | 37 +++++ .../src/test/resources/app.properties | 5 + .../PathVariableReplacementTest.java | 1 + .../impl/AddHeaderOperationTest.java | 2 + .../impl/test/DeleteOperationTest.java | 1 + .../impl/test/PatchOperationTest.java | 4 +- .../test/AbstractOperationTimeoutTest.java | 1 + .../test/CreateBolt11InvoiceRequestTest.java | 11 +- .../rest/test/DecodeInvoiceRequestTest.java | 1 + .../test/GetLightningAddressResponseTest.java | 1 + .../test/PayBolt11InvoiceRequestTest.java | 1 + .../rest/test/PayLightningAddressTest.java | 10 +- .../impl/rest/test/PayRequestFactoryTest.java | 6 + 32 files changed, 503 insertions(+), 5 deletions(-) create mode 100644 phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/operation/OperationsTest.java create mode 100644 phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/AbstractRequestTest.java create mode 100644 phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/impl/RequestTest.java create mode 100644 phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/PayRequestFactoryTest.java create mode 100644 phoenixd-rest/src/test/resources/app.properties diff --git a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/OperationTest.java b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/OperationTest.java index 1f3ec91..bdfaecc 100644 --- a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/OperationTest.java +++ b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/OperationTest.java @@ -11,6 +11,7 @@ static class SampleParam implements Request.Param { String slug = "abc"; } + // Checks that path variables are replaced with matching parameter fields @Test void replacePathVariablesSubstitutesFields() throws Exception { Operation op = new Operation() { diff --git a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/RequestParamTest.java b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/RequestParamTest.java index cb27d58..6bc90f8 100644 --- a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/RequestParamTest.java +++ b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/RequestParamTest.java @@ -6,12 +6,14 @@ class RequestParamTest { + // Ensures a request parameter defaults to using the PATH kind @Test void defaultKindIsPath() { Request.Param param = new Request.Param() {}; assertThat(param.getKind()).isEqualTo(Request.Param.Kind.PATH); } + // Verifies enum access and placeholder classes work as expected @Test void enumValuesAndVoidClasses() { assertThat(Request.Param.Kind.valueOf("QUERY")).isEqualTo(Request.Param.Kind.QUERY); diff --git a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/RequestTest.java b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/RequestTest.java index e4674f5..ea2f6e7 100644 --- a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/RequestTest.java +++ b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/RequestTest.java @@ -6,6 +6,7 @@ class RequestTest { + // Ensures the default getResponse implementation throws an exception @Test void defaultGetResponseThrowsUnsupportedOperationException() { Request request = new Request() {}; diff --git a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/util/ConfigurationTest.java b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/util/ConfigurationTest.java index 592a850..bb8ed44 100644 --- a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/util/ConfigurationTest.java +++ b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/util/ConfigurationTest.java @@ -18,6 +18,7 @@ void setUp() { configuration = new Configuration("test", url); } + // Verifies all getter methods return expected defaults or file values @Test void keysAndGettersHandleDefaultsProperly() { assertThat(configuration.keys()).containsExactlyInAnyOrder("string", "int", "long", "double", "boolean"); @@ -47,12 +48,14 @@ void keysAndGettersHandleDefaultsProperly() { assertThat(configuration.getBoolean("boolean", false)).isTrue(); } + // Ensures the default constructor loads properties from the classpath @Test void defaultConstructorLoadsAppProperties() { Configuration cfg = new Configuration("test"); assertThat(cfg.get("string")).isEqualTo("fromFile"); } + // Confirms environment variables take precedence over property files @Test void environmentVariablesOverrideProperties() throws Exception { String classpath = System.getProperty("java.class.path"); diff --git a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/util/ConstantsTest.java b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/util/ConstantsTest.java index 36c7283..4ad80c0 100644 --- a/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/util/ConstantsTest.java +++ b/phoenixd-base/src/test/java/xyz/tcheeric/phoenixd/common/rest/util/ConstantsTest.java @@ -6,6 +6,7 @@ class ConstantsTest { + // Validates that HTTP method constants expose the correct strings @Test void httpMethodConstantsMatchValues() { assertThat(Constants.HTTP_GET_METHOD).isEqualTo("GET"); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/CreateInvoiceParamTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/CreateInvoiceParamTest.java index 0435bd9..a15e10c 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/CreateInvoiceParamTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/CreateInvoiceParamTest.java @@ -9,6 +9,7 @@ class CreateInvoiceParamTest { + // Ensures toString properly URL-encodes provided values @Test void toStringEncodesValues() throws MalformedURLException { CreateInvoiceParam param = new CreateInvoiceParam(); @@ -26,6 +27,7 @@ void toStringEncodesValues() throws MalformedURLException { .contains("webhookUrl=https%3A%2F%2Fexample.com%2Fhook%3Ffoo%3Dbar%26baz%3Dqux"); } + // Verifies getters, setters and toString when a webhook URL is supplied @Test void settersGettersAndToStringWithWebhook() throws Exception { CreateInvoiceParam param = new CreateInvoiceParam(); @@ -46,6 +48,7 @@ void settersGettersAndToStringWithWebhook() throws Exception { "description=desc&amountSat=1&expirySeconds=2&externalId=id&webhookUrl=https://example.com"); } + // Confirms toString omits the webhook URL when it is not set @Test void toStringWithoutWebhook() { CreateInvoiceParam param = new CreateInvoiceParam(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/DecodeInvoiceParamTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/DecodeInvoiceParamTest.java index d597640..4263168 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/DecodeInvoiceParamTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/DecodeInvoiceParamTest.java @@ -6,6 +6,7 @@ class DecodeInvoiceParamTest { + // Ensures the invoice field is handled correctly in getters and toString @Test void settersGettersAndToString() { DecodeInvoiceParam param = new DecodeInvoiceParam(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/PayBolt11InvoiceParamTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/PayBolt11InvoiceParamTest.java index b584c24..4a26787 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/PayBolt11InvoiceParamTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/PayBolt11InvoiceParamTest.java @@ -6,6 +6,7 @@ class PayBolt11InvoiceParamTest { + // Validates getters, setters and string representation of the param @Test void settersGettersAndToString() { PayBolt11InvoiceParam param = new PayBolt11InvoiceParam(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/PayLightningAddressParamTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/PayLightningAddressParamTest.java index 9652c90..c13f874 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/PayLightningAddressParamTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/param/PayLightningAddressParamTest.java @@ -6,6 +6,7 @@ class PayLightningAddressParamTest { + // Ensures setters, getters, and toString handle a message field @Test void settersGettersAndToStringWithMessage() { PayLightningAddressParam param = new PayLightningAddressParam(); @@ -19,6 +20,7 @@ void settersGettersAndToStringWithMessage() { assertThat(param.toString()).isEqualTo("amountSat=70&address=addr&message=hello"); } + // Confirms toString omits the message when it is empty or null @Test void toStringWithoutMessage() { PayLightningAddressParam param = new PayLightningAddressParam(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/CreateInvoiceResponseTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/CreateInvoiceResponseTest.java index 7cce56b..a1951c3 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/CreateInvoiceResponseTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/CreateInvoiceResponseTest.java @@ -6,6 +6,7 @@ class CreateInvoiceResponseTest { + // Validates fields and string representation for invoice creation responses @Test void settersGettersAndToString() { CreateInvoiceResponse response = new CreateInvoiceResponse(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/DecodeInvoiceResponseTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/DecodeInvoiceResponseTest.java index a2c0bc6..7a496c8 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/DecodeInvoiceResponseTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/DecodeInvoiceResponseTest.java @@ -8,6 +8,7 @@ class DecodeInvoiceResponseTest { + // Checks getters, setters, and string output for complex invoice responses @Test void settersGettersAndToString() { DecodeInvoiceResponse response = new DecodeInvoiceResponse(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/GetLightningAddressResponseTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/GetLightningAddressResponseTest.java index 38d7158..6d1a134 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/GetLightningAddressResponseTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/GetLightningAddressResponseTest.java @@ -6,6 +6,7 @@ class GetLightningAddressResponseTest { + // Ensures lightning addresses are handled and formatted correctly @Test void settersGettersAndToString() { GetLightningAddressResponse response = new GetLightningAddressResponse(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayBolt11InvoiceInvoiceResponseTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayBolt11InvoiceInvoiceResponseTest.java index 46377cb..fb00876 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayBolt11InvoiceInvoiceResponseTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayBolt11InvoiceInvoiceResponseTest.java @@ -6,6 +6,7 @@ class PayBolt11InvoiceInvoiceResponseTest { + // Validates Bolt11 payment response fields and string output @Test void settersGettersAndToString() { PayBolt11InvoiceInvoiceResponse response = new PayBolt11InvoiceInvoiceResponse(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayInvoiceResponseTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayInvoiceResponseTest.java index b0f4995..738bfb0 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayInvoiceResponseTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayInvoiceResponseTest.java @@ -6,6 +6,7 @@ class PayInvoiceResponseTest { + // Confirms fields and string output for generic payment invoices @Test void settersGettersAndToString() { PayInvoiceResponse response = new PayInvoiceResponse(); diff --git a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayLightningAddressInvoiceResponseTest.java b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayLightningAddressInvoiceResponseTest.java index 790eb58..bea7100 100644 --- a/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayLightningAddressInvoiceResponseTest.java +++ b/phoenixd-model/src/test/java/xyz/tcheeric/phoenixd/model/response/PayLightningAddressInvoiceResponseTest.java @@ -6,6 +6,7 @@ class PayLightningAddressInvoiceResponseTest { + // Checks values and string output for lightning address payment invoices @Test void settersGettersAndToString() { PayLightningAddressInvoiceResponse response = new PayLightningAddressInvoiceResponse(); diff --git a/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/operation/AbstractOperationTest.java b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/operation/AbstractOperationTest.java index dcf39b3..033a072 100644 --- a/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/operation/AbstractOperationTest.java +++ b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/operation/AbstractOperationTest.java @@ -4,13 +4,16 @@ import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.Test; import xyz.tcheeric.phoenixd.operation.impl.GetOperation; +import xyz.tcheeric.phoenixd.operation.impl.PostOperation; import java.net.http.HttpRequest; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AbstractOperationTest { + // Confirms operations execute once and capture the response body @Test void executeMakesSingleNetworkCall() throws Exception { MockWebServer server = new MockWebServer(); @@ -31,4 +34,36 @@ void executeMakesSingleNetworkCall() throws Exception { server.shutdown(); } } + + // Ensures non-success HTTP status codes trigger exceptions + @Test + void executeThrowsOnNon2xxResponse() throws Exception { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(400)); + server.start(); + try { + HttpRequest request = HttpRequest.newBuilder(server.url("/fail").uri()).GET().build(); + GetOperation operation = new GetOperation(request); + assertThatThrownBy(operation::execute).isInstanceOf(Exception.class); + } finally { + server.shutdown(); + } + } + + // Verifies adding a header replaces any existing value + @Test + void addHeaderReplacesExisting() { + PostOperation op = new PostOperation("/items", "data"); + String original = op.getHeader("Authorization"); + op.addHeader("Authorization", "Basic new"); + assertThat(op.getHeader("Authorization")).isEqualTo("Basic new"); + assertThat(original).isNotEqualTo(op.getHeader("Authorization")); + } + + // Checks that removing headers from a POST operation is unsupported + @Test + void removeHeaderUnsupported() { + PostOperation op = new PostOperation("/items", "data"); + assertThatThrownBy(() -> op.removeHeader("X")).isInstanceOf(UnsupportedOperationException.class); + } } diff --git a/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/operation/OperationsTest.java b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/operation/OperationsTest.java new file mode 100644 index 0000000..d3dde27 --- /dev/null +++ b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/operation/OperationsTest.java @@ -0,0 +1,153 @@ +package xyz.tcheeric.phoenixd.operation; + +import org.junit.jupiter.api.Test; +import xyz.tcheeric.phoenixd.common.rest.Request; +import xyz.tcheeric.phoenixd.operation.impl.DeleteOperation; +import xyz.tcheeric.phoenixd.operation.impl.PatchOperation; +import xyz.tcheeric.phoenixd.operation.impl.PostOperation; +import xyz.tcheeric.phoenixd.operation.impl.GetOperation; + +import java.net.URI; +import java.net.http.HttpRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class OperationsTest { + + static class PathParam implements Request.Param { + String id = "123"; + @Override + public String toString() { + return "id=" + id; + } + } + + // Verifies POST operations set content type and expand path variables + @Test + void postOperationSetsContentTypeAndResolvesPath() { + PostOperation op = new PostOperation("/items/{id}", new PathParam(), "data"); + assertThat(op.getHeader("Content-Type")).isEqualTo("application/x-www-form-urlencoded"); + assertThat(op.getHttpRequest().uri().getPath()).isEqualTo("/items/123"); + } + + // Ensures POST operations with body still assign content type header + @Test + void postOperationWithBodyOnlySetsHeader() { + PostOperation op = new PostOperation("/items", "data"); + assertThat(op.getHeader("Content-Type")).isEqualTo("application/x-www-form-urlencoded"); + } + + // Confirms constructing a POST operation from an HttpRequest retains the path + @Test + void postOperationFromHttpRequest() { + HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost/post")) + .POST(HttpRequest.BodyPublishers.noBody()) + .build(); + PostOperation op = new PostOperation(request); + assertThat(op.getHttpRequest().uri().getPath()).isEqualTo("/post"); + } + + // Checks that PATCH operations support adding headers + @Test + void patchOperationAddsHeader() { + PatchOperation op = new PatchOperation("/patch"); + op.addHeader("X-Test", "value"); + assertThat(op.getHeader("X-Test")).isEqualTo("value"); + } + + // Validates removing headers from PATCH operations is unsupported + @Test + void patchOperationRemoveHeaderThrows() { + PatchOperation op = new PatchOperation("/patch"); + assertThatThrownBy(() -> op.removeHeader("X")) + .isInstanceOf(UnsupportedOperationException.class); + } + + // Ensures DELETE operations allow removal of default headers + @Test + void deleteOperationRemovesHeader() { + DeleteOperation op = new DeleteOperation("/delete"); + assertThat(op.getHeader("Authorization")).isNotNull(); + op.removeHeader("Authorization"); + assertThat(op.getHeader("Authorization")).isNull(); + } + + // Verifies DELETE operations resolve path variables from parameters + @Test + void deleteOperationWithParamResolvesPath() { + DeleteOperation op = new DeleteOperation("/items/{id}", new PathParam()); + assertThat(op.getHttpRequest().uri().getPath()).isEqualTo("/items/123"); + } + + // Confirms DELETE operations built from HttpRequest keep the same path + @Test + void deleteOperationFromHttpRequest() { + HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost/delete")) + .DELETE() + .build(); + DeleteOperation op = new DeleteOperation(request); + assertThat(op.getHttpRequest().uri().getPath()).isEqualTo("/delete"); + } + + // Ensures simple GET operations keep the provided path + @Test + void getOperationFromPath() { + GetOperation op = new GetOperation("/items"); + assertThat(op.getHttpRequest().uri().getPath()).isEqualTo("/items"); + } + + // Confirms GET operations created from an HttpRequest retain their path + @Test + void getOperationFromHttpRequest() { + HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost/get")) + .GET() + .build(); + GetOperation op = new GetOperation(request); + assertThat(op.getHttpRequest().uri().getPath()).isEqualTo("/get"); + } + + // Validates PATCH operations resolve path variables when given parameters + @Test + void patchOperationWithParamResolvesPath() { + PatchOperation op = new PatchOperation("/patch/{id}", new PathParam()); + assertThat(op.getHttpRequest().uri().getPath()).isEqualTo("/patch/123"); + } + + // Confirms DELETE operations do not support adding headers + @Test + void deleteOperationAddHeaderThrows() { + DeleteOperation op = new DeleteOperation("/delete"); + assertThatThrownBy(() -> op.addHeader("X", "Y")).isInstanceOf(UnsupportedOperationException.class); + } + + // Checks GET operations append query parameters for QUERY-kind params + @Test + void getOperationWithQueryParamAppendsQuery() { + class QueryParam extends PathParam { + @Override + public Request.Param.Kind getKind() { + return Request.Param.Kind.QUERY; + } + } + GetOperation op = new GetOperation("/query", new QueryParam()); + assertThat(op.getHttpRequest().uri().getQuery()).isEqualTo("id=123"); + } + + // Ensures constructors validate against null arguments + @Test + void nullArgumentsThrow() { + assertThatThrownBy(() -> new PostOperation(null, "data")).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PostOperation("/x", (Request.Param) null, "data")).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PostOperation("/x", new PathParam(), null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PostOperation((HttpRequest) null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PatchOperation((String) null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PatchOperation("/x", null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new DeleteOperation((String) null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new DeleteOperation("/x", null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new DeleteOperation((HttpRequest) null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new GetOperation((String) null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new GetOperation("/x", null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new GetOperation((HttpRequest) null)).isInstanceOf(NullPointerException.class); + } +} diff --git a/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/AbstractRequestTest.java b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/AbstractRequestTest.java new file mode 100644 index 0000000..789a848 --- /dev/null +++ b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/AbstractRequestTest.java @@ -0,0 +1,96 @@ +package xyz.tcheeric.phoenixd.request; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.Test; +import xyz.tcheeric.phoenixd.common.rest.Operation; +import xyz.tcheeric.phoenixd.common.rest.VoidRequestParam; +import xyz.tcheeric.phoenixd.common.rest.VoidResponse; +import xyz.tcheeric.phoenixd.model.response.CreateInvoiceResponse; +import xyz.tcheeric.phoenixd.model.response.GetLightningAddressResponse; +import xyz.tcheeric.phoenixd.operation.impl.GetOperation; +import xyz.tcheeric.phoenixd.operation.AbstractOperation; + +import java.net.URI; +import java.net.http.HttpRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +class AbstractRequestTest { + + // Parses JSON bodies into response objects + @Test + void parsesJsonResponse() throws Exception { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"amountSat\":1,\"paymentHash\":\"h\",\"serialized\":\"s\"}")); + server.start(); + try { + HttpRequest httpRequest = HttpRequest.newBuilder(server.url("/invoice").uri()).GET().build(); + GetOperation op = new GetOperation(httpRequest); + AbstractRequest req = new AbstractRequest<>("/invoice", null, op) {}; + CreateInvoiceResponse resp = req.getResponse(); + assertThat(resp.getAmountSat()).isEqualTo(1); + assertThat(resp.getPaymentHash()).isEqualTo("h"); + assertThat(server.getRequestCount()).isEqualTo(1); + } finally { + server.shutdown(); + } + } + + // Removes bitcoin prefix from lightning address responses + @Test + void handlesLightningAddressWithPrefix() throws Exception { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody("â‚¿user@domain")); + server.start(); + try { + HttpRequest httpRequest = HttpRequest.newBuilder(server.url("/ln").uri()).GET().build(); + GetOperation op = new GetOperation(httpRequest); + AbstractRequest req = new AbstractRequest<>("/ln", null, op) {}; + GetLightningAddressResponse resp = req.getResponse(); + assertThat(resp.getLightningAddress()).isEqualTo("user@domain"); + assertThat(server.getRequestCount()).isEqualTo(1); + } finally { + server.shutdown(); + } + } + + // Accepts lightning addresses returned without any prefix + @Test + void handlesLightningAddressWithoutPrefix() throws Exception { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody("user@domain")); + server.start(); + try { + HttpRequest httpRequest = HttpRequest.newBuilder(server.url("/ln").uri()).GET().build(); + GetOperation op = new GetOperation(httpRequest); + AbstractRequest req = new AbstractRequest<>("/ln", null, op) {}; + GetLightningAddressResponse resp = req.getResponse(); + assertThat(resp.getLightningAddress()).isEqualTo("user@domain"); + assertThat(server.getRequestCount()).isEqualTo(1); + } finally { + server.shutdown(); + } + } + + // Ensures operations aren't executed when expecting a void response + @Test + void voidResponseDoesNotExecuteOperation() { + class DummyOperation extends AbstractOperation { + boolean executed = false; + DummyOperation() { + super(HttpRequest.newBuilder(URI.create("http://localhost/test")).GET().build()); + } + @Override + public Operation execute() { + executed = true; + return this; + } + } + DummyOperation op = new DummyOperation(); + AbstractRequest req = new AbstractRequest<>("/test", null, op) {}; + VoidResponse resp = req.getResponse(); + assertThat(resp).isNotNull(); + assertThat(op.executed).isFalse(); + } +} diff --git a/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/impl/RequestTest.java b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/impl/RequestTest.java new file mode 100644 index 0000000..7605ce6 --- /dev/null +++ b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/impl/RequestTest.java @@ -0,0 +1,122 @@ +package xyz.tcheeric.phoenixd.request.impl; + +import org.junit.jupiter.api.Test; +import xyz.tcheeric.phoenixd.common.rest.Request; +import xyz.tcheeric.phoenixd.common.rest.VoidRequestParam; +import xyz.tcheeric.phoenixd.common.rest.VoidResponse; +import xyz.tcheeric.phoenixd.model.param.CreateInvoiceParam; +import xyz.tcheeric.phoenixd.model.param.DecodeInvoiceParam; +import xyz.tcheeric.phoenixd.request.impl.rest.CreateBolt11InvoiceRequest; +import xyz.tcheeric.phoenixd.request.impl.rest.DecodeInvoiceRequest; +import xyz.tcheeric.phoenixd.request.impl.rest.GetLightningAddressRequest; +import xyz.tcheeric.phoenixd.operation.AbstractOperation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class RequestTest { + + static class DummyParam implements Request.Param { + String id = "123"; + @Override + public String toString() { + return ""; + } + } + + // Verifies header management and path substitution for POST and DELETE requests + @Test + void postAndDeleteRequestHeaders() { + PostRequest post = new PostRequest<>("/post", "body"); + post.addHeader("X-Test", "value"); + assertThat(post.getOperation().getHeader("X-Test")).isEqualTo("value"); + + DummyParam param = new DummyParam(); + PostRequest postWithParam = new PostRequest<>("/post", param); + postWithParam.addHeader("A", "B"); + PostRequest postWithParamAndBody = new PostRequest<>("/post/{id}", param, "data"); + postWithParamAndBody.addHeader("A", "B"); + assertThat(((xyz.tcheeric.phoenixd.operation.AbstractOperation) postWithParam.getOperation()).getHttpRequest().uri().getPath()).isEqualTo("/post"); + assertThat(((xyz.tcheeric.phoenixd.operation.AbstractOperation) postWithParamAndBody.getOperation()).getHttpRequest().uri().getPath()).isEqualTo("/post/123"); + assertThat(postWithParam.getParam()).isEqualTo(param); + assertThat(postWithParamAndBody.getParam()).isEqualTo(param); + + DeleteRequest delete = new DeleteRequest<>("/delete"); + assertThat(delete.getOperation().getHeader("Authorization")).isNotNull(); + delete.removeHeader("Authorization"); + assertThat(delete.getOperation().getHeader("Authorization")).isNull(); + + DeleteRequest deleteWithParam = new DeleteRequest<>("/delete/{id}", new DummyParam()); + assertThat(((xyz.tcheeric.phoenixd.operation.AbstractOperation) deleteWithParam.getOperation()).getHttpRequest().uri().getPath()).isEqualTo("/delete/123"); + assertThat(deleteWithParam.getParam()).isNotNull(); + } + + // Checks GET and PATCH requests handle parameters and paths + @Test + void getAndPatchRequestConstructors() { + GetRequest getNoParam = new GetRequest<>("/get"); + GetRequest get = new GetRequest<>("/get", new DummyParam()); + PatchRequest patchNoParam = new PatchRequest<>("/patch"); + PatchRequest patch = new PatchRequest<>("/patch", new DummyParam()); + assertThat(getNoParam.getPath()).isEqualTo("/get"); + assertThat(((AbstractOperation) getNoParam.getOperation()).getHttpRequest().uri().getPath()).isEqualTo("/get"); + assertThat(get.getPath()).isEqualTo("/get"); + assertThat(((AbstractOperation) get.getOperation()).getHttpRequest().uri().getPath()).isEqualTo("/get"); + assertThat(get.getParam()).isNotNull(); + assertThat(patchNoParam.getPath()).isEqualTo("/patch"); + assertThat(((AbstractOperation) patchNoParam.getOperation()).getHttpRequest().uri().getPath()).isEqualTo("/patch"); + assertThat(patch.getPath()).isEqualTo("/patch"); + assertThat(((AbstractOperation) patch.getOperation()).getHttpRequest().uri().getPath()).isEqualTo("/patch"); + assertThat(patch.getParam()).isNotNull(); + } + + // Ensures specialized request subclasses can be constructed + @Test + void specializedRequestsConstructors() { + CreateInvoiceParam createParam = new CreateInvoiceParam(); + createParam.setDescription("desc"); + new CreateBolt11InvoiceRequest(createParam); + + DecodeInvoiceParam decodeParam = new DecodeInvoiceParam(); + decodeParam.setInvoice("lnbc1abcde"); + new DecodeInvoiceRequest(decodeParam); + + new GetLightningAddressRequest(); + } + + // Confirms POST request constructors throw on null arguments + @Test + void postRequestNullArgumentsThrow() { + DummyParam param = new DummyParam(); + assertThatThrownBy(() -> new PostRequest(null, param)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PostRequest("/post", (String) null)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PostRequest(null, param, "data")) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PostRequest("/post", (DummyParam) null, "data")) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PostRequest("/post", param, null)) + .isInstanceOf(NullPointerException.class); + } + + // Ensures PostRequest.addHeader validates inputs + @Test + void postRequestAddHeaderNullArguments() { + PostRequest request = new PostRequest<>("/post", "body"); + assertThatThrownBy(() -> request.addHeader(null, "v")).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> request.addHeader("k", null)).isInstanceOf(NullPointerException.class); + } + + // Validates other request types enforce non-null parameters + @Test + void otherRequestsNullArgumentsThrow() { + DummyParam param = new DummyParam(); + assertThatThrownBy(() -> new GetRequest(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new GetRequest("/get", null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PatchRequest(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PatchRequest("/patch", null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new DeleteRequest(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new DeleteRequest("/del", null)).isInstanceOf(NullPointerException.class); + } +} diff --git a/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/PayRequestFactoryTest.java b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/PayRequestFactoryTest.java new file mode 100644 index 0000000..f31ca6e --- /dev/null +++ b/phoenixd-rest/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/PayRequestFactoryTest.java @@ -0,0 +1,37 @@ +package xyz.tcheeric.phoenixd.request.impl.rest; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PayRequestFactoryTest { + + // Ensures lightning addresses produce the proper request type + @Test + void createsLightningAddressRequest() { + BasePayRequest request = PayRequestFactory.createPayRequest("user@example.com"); + assertThat(request).isInstanceOf(PayLightningAddressRequest.class); + } + + // Checks that Bolt11 invoices are routed to the correct request + @Test + void createsBolt11InvoiceRequest() { + BasePayRequest request = PayRequestFactory.createPayRequest("lnbc1abcde"); + assertThat(request).isInstanceOf(PayBolt11InvoiceRequest.class); + } + + // Verifies invalid strings result in an IllegalArgumentException + @Test + void throwsOnInvalidRequest() { + assertThatThrownBy(() -> PayRequestFactory.createPayRequest("invalid")) + .isInstanceOf(IllegalArgumentException.class); + } + + // Confirms null inputs are rejected with a NullPointerException + @Test + void nullRequestThrows() { + assertThatThrownBy(() -> PayRequestFactory.createPayRequest(null)) + .isInstanceOf(NullPointerException.class); + } +} diff --git a/phoenixd-rest/src/test/resources/app.properties b/phoenixd-rest/src/test/resources/app.properties new file mode 100644 index 0000000..70a85b7 --- /dev/null +++ b/phoenixd-rest/src/test/resources/app.properties @@ -0,0 +1,5 @@ +phoenixd.username=user +phoenixd.password=pass +phoenixd.base_url=http://localhost +phoenixd.timeout=5000 +phoenixd.webhook_secret= diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/PathVariableReplacementTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/PathVariableReplacementTest.java index 5352a6b..11cde47 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/PathVariableReplacementTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/PathVariableReplacementTest.java @@ -23,6 +23,7 @@ public String toString() { } } + // Verifies multiple path variables are replaced in the URI @Test public void replacesMultipleVariables() { MultiPathParam param = new MultiPathParam("123", "456"); diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/AddHeaderOperationTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/AddHeaderOperationTest.java index 201de97..091802d 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/AddHeaderOperationTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/AddHeaderOperationTest.java @@ -7,6 +7,7 @@ public class AddHeaderOperationTest { + // Ensures adding a header preserves the HTTP method for GET operations @Test void addHeaderDoesNotChangeMethodForGetOperation() { GetOperation operation = new GetOperation("/test"); @@ -17,6 +18,7 @@ void addHeaderDoesNotChangeMethodForGetOperation() { assertThat(operation.getHeader("X-Test")).isEqualTo("value"); } + // Verifies PATCH operations keep their method after adding headers @Test void addHeaderDoesNotChangeMethodForPatchOperation() { PatchOperation operation = new PatchOperation("/test"); diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/test/DeleteOperationTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/test/DeleteOperationTest.java index 5f8495e..1c048d0 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/test/DeleteOperationTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/test/DeleteOperationTest.java @@ -31,6 +31,7 @@ public TestDeleteRequest() { } } + // Verifies DELETE operations handle headers and responses appropriately @Test public void testDeleteOperation() { TestDeleteRequest request = new TestDeleteRequest(); diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/test/PatchOperationTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/test/PatchOperationTest.java index 096d953..6ec620c 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/test/PatchOperationTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/test/PatchOperationTest.java @@ -31,6 +31,7 @@ public TestPatchRequest() { } } + // Ensures PATCH requests use proper method, headers, and response mapping @Test public void testPatchOperation() { TestPatchRequest request = new TestPatchRequest(); @@ -40,7 +41,8 @@ public void testPatchOperation() { assertEquals("PATCH", ((PatchOperation) request.getOperation()).getHttpRequest().method()); assertNotNull(request.getOperation().getHeader("Authorization")); - assertThrows(UnsupportedOperationException.class, () -> request.getOperation().addHeader("x", "y")); + assertDoesNotThrow(() -> request.getOperation().addHeader("x", "y")); + assertEquals("y", request.getOperation().getHeader("x")); assertThrows(UnsupportedOperationException.class, () -> request.getOperation().removeHeader("Authorization")); PatchResponse response = request.getResponse(); diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/test/AbstractOperationTimeoutTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/test/AbstractOperationTimeoutTest.java index 5925b1e..a7c8af2 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/test/AbstractOperationTimeoutTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/test/AbstractOperationTimeoutTest.java @@ -13,6 +13,7 @@ static class TestOperation extends AbstractOperation { } } + // Ensures operations use the default timeout when none is set @Test void usesDefaultTimeoutWhenMissing() { TestOperation operation = new TestOperation(); diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/CreateBolt11InvoiceRequestTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/CreateBolt11InvoiceRequestTest.java index fbdc205..d1da93a 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/CreateBolt11InvoiceRequestTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/CreateBolt11InvoiceRequestTest.java @@ -7,6 +7,7 @@ import xyz.tcheeric.phoenixd.request.impl.rest.CreateBolt11InvoiceRequest; import xyz.tcheeric.phoenixd.test.LocalTestServerExtension; import xyz.tcheeric.phoenixd.test.TestUtils; +import xyz.tcheeric.phoenixd.operation.AbstractOperation; import com.sun.net.httpserver.HttpServer; @@ -23,7 +24,9 @@ public class CreateBolt11InvoiceRequestTest { private static final Logger log = Logger.getLogger(CreateBolt11InvoiceRequestTest.class.getName()); + private static final int ERROR_SERVER_PORT = 9751; + // Checks constructor initializes request with correct path @Test public void testConstructor() { // Arrange and Act @@ -33,6 +36,7 @@ public void testConstructor() { assertEquals("/createinvoice", createBolt11InvoiceRequest.getPath()); } + // Ensures headers can be added to the underlying operation @Test public void testAddHeader() { // Arrange @@ -45,6 +49,7 @@ public void testAddHeader() { assertEquals("value", createBolt11InvoiceRequest.getOperation().getHeader("key")); } + // Validates response parsing for a successful invoice creation @Test public void testGetResponse() throws Exception { // Arrange @@ -65,17 +70,19 @@ public void testGetResponse() throws Exception { log.log(Level.ALL, "Invoice: {0}", response.getSerialized()); } + // Verifies request URI and default headers are set properly @Test public void testUriAndHeaders() { // Arrange CreateBolt11InvoiceRequest request = new CreateBolt11InvoiceRequest(new CreateInvoiceParam()); // Assert - assertEquals("http://localhost:9740/createinvoice", request.getOperation().getHttpRequest().uri().toString()); + assertEquals("http://localhost:9740/createinvoice", ((AbstractOperation) request.getOperation()).getHttpRequest().uri().toString()); assertEquals("Basic Og==", request.getOperation().getHeader("Authorization")); assertEquals("application/x-www-form-urlencoded", request.getOperation().getHeader("Content-Type")); } + // Confirms IOExceptions are thrown when server returns an error @Test public void testErrorHandling() throws Exception { HttpServer errorServer = HttpServer.create(new InetSocketAddress(ERROR_SERVER_PORT), 0); @@ -88,7 +95,7 @@ public void testErrorHandling() throws Exception { }); errorServer.start(); try { - TestUtils.setBaseUrl("http://localhost:" + ERROR_PORT); + TestUtils.setBaseUrl("http://localhost:" + ERROR_SERVER_PORT); CreateBolt11InvoiceRequest request = new CreateBolt11InvoiceRequest(new CreateInvoiceParam()); assertThrows(IOException.class, request::getResponse); } finally { diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/DecodeInvoiceRequestTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/DecodeInvoiceRequestTest.java index 356b44b..57523c5 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/DecodeInvoiceRequestTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/DecodeInvoiceRequestTest.java @@ -12,6 +12,7 @@ @ExtendWith(LocalTestServerExtension.class) public class DecodeInvoiceRequestTest { + // Ensures a decode invoice request posts data and returns parsed response @Test public void testConstructor() { // Arrange diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/GetLightningAddressResponseTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/GetLightningAddressResponseTest.java index 7c4c25c..ff5bbb5 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/GetLightningAddressResponseTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/GetLightningAddressResponseTest.java @@ -13,6 +13,7 @@ @ExtendWith(LocalTestServerExtension.class) public class GetLightningAddressResponseTest { + // Ensures the request retrieves a lightning address and has correct path @Test public void testConstructor() { // Arrange diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayBolt11InvoiceRequestTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayBolt11InvoiceRequestTest.java index 406bb9d..9767d2a 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayBolt11InvoiceRequestTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayBolt11InvoiceRequestTest.java @@ -12,6 +12,7 @@ @ExtendWith(LocalTestServerExtension.class) public class PayBolt11InvoiceRequestTest { + // Verifies the request initializes with the proper path, param, and operation @Test public void testConstructor() { // Arrange diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayLightningAddressTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayLightningAddressTest.java index a1cca29..7305f6c 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayLightningAddressTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayLightningAddressTest.java @@ -5,6 +5,7 @@ import xyz.tcheeric.phoenixd.common.rest.util.Configuration; import xyz.tcheeric.phoenixd.model.param.PayLightningAddressParam; import xyz.tcheeric.phoenixd.model.response.PayLightningAddressInvoiceResponse; +import xyz.tcheeric.phoenixd.operation.AbstractOperation; import xyz.tcheeric.phoenixd.operation.impl.PostOperation; import xyz.tcheeric.phoenixd.request.impl.rest.PayLightningAddressRequest; import xyz.tcheeric.phoenixd.test.LocalTestServerExtension; @@ -22,6 +23,9 @@ @ExtendWith(LocalTestServerExtension.class) public class PayLightningAddressTest { + private static final int ERROR_SERVER_PORT = 9751; + + // Validates the request sends correct data and parses the payment response @Test public void testConstructor() { // Arrange @@ -42,6 +46,7 @@ public void testConstructor() { assertEquals(10, response.getRecipientAmountSat()); } + // Checks default URI and headers on the built request @Test public void testUriAndHeaders() { PayLightningAddressParam param = new PayLightningAddressParam(); @@ -50,14 +55,15 @@ public void testUriAndHeaders() { param.setAmountSat(10); PayLightningAddressRequest request = new PayLightningAddressRequest(param); - assertEquals("http://localhost:9740/paylnaddress", request.getOperation().getHttpRequest().uri().toString()); + assertEquals("http://localhost:9740/paylnaddress", ((AbstractOperation) request.getOperation()).getHttpRequest().uri().toString()); assertEquals("Basic Og==", request.getOperation().getHeader("Authorization")); assertEquals("application/x-www-form-urlencoded", request.getOperation().getHeader("Content-Type")); } + // Ensures IOExceptions are raised when the server responds with an error @Test public void testErrorHandling() throws Exception { - HttpServer errorServer = HttpServer.create(new InetSocketAddress(9751), 0); + HttpServer errorServer = HttpServer.create(new InetSocketAddress(ERROR_SERVER_PORT), 0); errorServer.createContext("/paylnaddress", exchange -> { byte[] bytes = "error".getBytes(StandardCharsets.UTF_8); exchange.sendResponseHeaders(500, bytes.length); diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayRequestFactoryTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayRequestFactoryTest.java index 318e8e6..d062906 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayRequestFactoryTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/request/impl/rest/test/PayRequestFactoryTest.java @@ -7,11 +7,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import xyz.tcheeric.phoenixd.request.impl.rest.PayLightningAddressRequest; public class PayRequestFactoryTest { + // Ensures factory handles uppercase Bolt11 invoices @Test public void testCreatePayRequestWithUppercaseInvoice() { String invoice = "LNBC1TESTINVOICE"; @@ -22,16 +25,19 @@ public void testCreatePayRequestWithUppercaseInvoice() { assertEquals(PayBolt11InvoiceRequest.class, request.getClass()); } + // Verifies lightning address inputs produce corresponding requests @Test public void testCreateLightningAddressRequest() { assertTrue(PayRequestFactory.createPayRequest("alice@example.com") instanceof PayLightningAddressRequest); } + // Checks that lowercase invoices produce Bolt11 requests @Test public void testCreateBolt11InvoiceRequest() { assertTrue(PayRequestFactory.createPayRequest("lnbc1u1pw0kx7pp5") instanceof PayBolt11InvoiceRequest); } + // Confirms invalid strings cause an exception @Test public void testInvalidRequestThrows() { assertThrows(IllegalArgumentException.class, () -> PayRequestFactory.createPayRequest("invalid")); From a416074e8b19935481ce932d3f3c724602c7b3d5 Mon Sep 17 00:00:00 2001 From: Eric T Date: Thu, 21 Aug 2025 04:16:16 +0100 Subject: [PATCH 2/2] Fix AddHeaderOperationTest configuration --- .../phoenixd/operation/impl/AddHeaderOperationTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/AddHeaderOperationTest.java b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/AddHeaderOperationTest.java index 091802d..d95deb2 100644 --- a/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/AddHeaderOperationTest.java +++ b/phoenixd-test/src/test/java/xyz/tcheeric/phoenixd/operation/impl/AddHeaderOperationTest.java @@ -1,12 +1,19 @@ package xyz.tcheeric.phoenixd.operation.impl; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import xyz.tcheeric.phoenixd.common.rest.Operation; +import xyz.tcheeric.phoenixd.test.TestUtils; import static org.assertj.core.api.Assertions.assertThat; public class AddHeaderOperationTest { + @BeforeEach + void setUpBaseUrl() { + TestUtils.setBaseUrl("http://localhost"); + } + // Ensures adding a header preserves the HTTP method for GET operations @Test void addHeaderDoesNotChangeMethodForGetOperation() {