From 22dec07657e1e34785727103d89ef0aa450ba771 Mon Sep 17 00:00:00 2001 From: Cristiano Betta Date: Wed, 4 Nov 2026 15:02:37 +0000 Subject: [PATCH 1/3] test: delete e2e merchant accounts after each run Calls core-api's DELETE /merchant-accounts/{id} (gr4vy/core-api#9999) directly, since it is not in the public spec. Failures are logged and never fail the run. Co-Authored-By: Claude Opus 5.5 --- .../sdk/backoffice/MerchantAccountsTest.java | 1 + src/test/java/com/gr4vy/sdk/util/Harness.java | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/test/java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java b/src/test/java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java index 65c43b5a..df22f76f 100644 --- a/src/test/java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java +++ b/src/test/java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java @@ -52,6 +52,7 @@ void create() throws Exception { .call() .merchantAccount() .orElseThrow(() -> new IllegalStateException("create returned no merchant account")); + Harness.trackMerchantAccount(account.id(), Harness.merchant().privateKey); assertFalse(account.id().isEmpty(), "created merchant account id must be non-empty"); } diff --git a/src/test/java/com/gr4vy/sdk/util/Harness.java b/src/test/java/com/gr4vy/sdk/util/Harness.java index 82df406b..5251adb9 100644 --- a/src/test/java/com/gr4vy/sdk/util/Harness.java +++ b/src/test/java/com/gr4vy/sdk/util/Harness.java @@ -1,16 +1,25 @@ package com.gr4vy.sdk.util; import java.io.IOException; +import java.net.URI; +import java.net.URLEncoder; import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.SecureRandom; import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Assumptions; +import com.gr4vy.sdk.Auth; import com.gr4vy.sdk.BearerSecuritySource; import com.gr4vy.sdk.Gr4vy; import com.gr4vy.sdk.Gr4vy.AvailableServers; @@ -43,6 +52,8 @@ private Harness() {} /** The sandbox environment id the suite targets ({@code api.sandbox.e2e.gr4vy.app}). */ public static final String SERVER_ID = "e2e"; + private static final String API_BASE_URL = "https://api.sandbox.e2e.gr4vy.app"; + /** A provisioned merchant: a merchant-bound client plus its identifiers. */ public static final class TestMerchant { public final Gr4vy client; @@ -66,6 +77,9 @@ private static final class NoKeyException extends RuntimeException { private static TestMerchant shared; private static RuntimeException setupError; + /** Merchant account id to signing key, deleted when the JVM exits. */ + private static final Map createdMerchantAccounts = new LinkedHashMap<>(); + /** * Returns the shard's shared merchant, provisioning it on first use. Skips * the calling test when no signing key is available; any other setup failure @@ -165,6 +179,8 @@ private static TestMerchant setup() throws Exception { MerchantAccount account = res.merchantAccount() .orElseThrow(() -> new IllegalStateException("merchant account create returned no body")); + trackMerchantAccount(account.id(), privateKey); + Gr4vy merchant = newClient(privateKey, account.id()); // A deterministic mock-card service so card flows reach a real 2xx. @@ -184,4 +200,51 @@ private static TestMerchant setup() throws Exception { return new TestMerchant(merchant, account.id(), privateKey); } + + /** + * Records a merchant account the suite created so it is deleted when the JVM + * exits (each CI shard is its own JVM). + */ + public static synchronized void trackMerchantAccount(String merchantAccountId, String privateKey) { + if (createdMerchantAccounts.isEmpty()) { + Runtime.getRuntime().addShutdownHook(new Thread(Harness::deleteCreatedMerchantAccounts)); + } + createdMerchantAccounts.put(merchantAccountId, privateKey); + } + + private static void deleteCreatedMerchantAccounts() { + List> accounts; + synchronized (Harness.class) { + accounts = new ArrayList<>(createdMerchantAccounts.entrySet()); + } + for (Map.Entry account : accounts) { + deleteMerchantAccount(account.getKey(), account.getValue()); + } + } + + /** + * Deletes a merchant account. The endpoint is not in the public spec, so it is + * called directly. Failures are logged, never thrown: cleanup must not fail the run. + */ + private static void deleteMerchantAccount(String merchantAccountId, String privateKey) { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(API_BASE_URL + "/merchant-accounts/" + + URLEncoder.encode(merchantAccountId, StandardCharsets.UTF_8))) + .timeout(Duration.ofSeconds(30)) + .header("Authorization", "Bearer " + Auth.getToken(privateKey)) + .DELETE() + .build(); + HttpResponse response = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(20)) + .build() + .send(request, HttpResponse.BodyHandlers.discarding()); + if (response.statusCode() != 204 && response.statusCode() != 404) { + System.err.println("Failed to delete merchant account " + merchantAccountId + + ": HTTP " + response.statusCode()); + } + } catch (Exception e) { + System.err.println("Failed to delete merchant account " + merchantAccountId + ": " + e); + } + } } From e9fa423f1349022e15f9bf7392f2fb220508bd7e Mon Sep 17 00:00:00 2001 From: Cristiano Betta Date: Wed, 4 Nov 2026 15:45:05 +0000 Subject: [PATCH 2/3] test: arm e2e merchant-account cleanup before the create call A create whose response is lost still leaves the account on the server; deleting an id that was never created returns 404, which cleanup already treats as done. Co-Authored-By: Claude Opus 5.5 --- .../java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java | 2 +- src/test/java/com/gr4vy/sdk/util/Harness.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java b/src/test/java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java index df22f76f..c86f7d7f 100644 --- a/src/test/java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java +++ b/src/test/java/com/gr4vy/sdk/backoffice/MerchantAccountsTest.java @@ -44,6 +44,7 @@ void list() throws Exception { void create() throws Exception { Gr4vy client = Harness.client(); String id = randomMerchantId(); + Harness.trackMerchantAccount(id, Harness.merchant().privateKey); MerchantAccount account = client.merchantAccounts().create() .request(MerchantAccountCreate.builder() .id(id) @@ -52,7 +53,6 @@ void create() throws Exception { .call() .merchantAccount() .orElseThrow(() -> new IllegalStateException("create returned no merchant account")); - Harness.trackMerchantAccount(account.id(), Harness.merchant().privateKey); assertFalse(account.id().isEmpty(), "created merchant account id must be non-empty"); } diff --git a/src/test/java/com/gr4vy/sdk/util/Harness.java b/src/test/java/com/gr4vy/sdk/util/Harness.java index 5251adb9..bea11c8e 100644 --- a/src/test/java/com/gr4vy/sdk/util/Harness.java +++ b/src/test/java/com/gr4vy/sdk/util/Harness.java @@ -168,6 +168,8 @@ private static TestMerchant setup() throws Exception { String privateKey = loadPrivateKey(); String merchantAccountId = randomMerchantId(); + trackMerchantAccount(merchantAccountId, privateKey); + // Admin client (not merchant-scoped) creates the merchant account. Gr4vy admin = newClient(privateKey, null); CreateMerchantAccountResponse res = admin.merchantAccounts().create() @@ -179,8 +181,6 @@ private static TestMerchant setup() throws Exception { MerchantAccount account = res.merchantAccount() .orElseThrow(() -> new IllegalStateException("merchant account create returned no body")); - trackMerchantAccount(account.id(), privateKey); - Gr4vy merchant = newClient(privateKey, account.id()); // A deterministic mock-card service so card flows reach a real 2xx. From 06c31d1dbbff1296199877992901c5baf38a35be Mon Sep 17 00:00:00 2001 From: Cristiano Betta Date: Wed, 4 Nov 2026 16:05:55 +0000 Subject: [PATCH 3/3] test: derive the cleanup base URL from the SDK server template Reuses Checkout.API_BASE_URL so the delete call can't drift from the host the SDK client targets. Co-Authored-By: Claude Opus 5.5 --- src/test/java/com/gr4vy/sdk/util/Harness.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/com/gr4vy/sdk/util/Harness.java b/src/test/java/com/gr4vy/sdk/util/Harness.java index bea11c8e..0924d7da 100644 --- a/src/test/java/com/gr4vy/sdk/util/Harness.java +++ b/src/test/java/com/gr4vy/sdk/util/Harness.java @@ -52,8 +52,6 @@ private Harness() {} /** The sandbox environment id the suite targets ({@code api.sandbox.e2e.gr4vy.app}). */ public static final String SERVER_ID = "e2e"; - private static final String API_BASE_URL = "https://api.sandbox.e2e.gr4vy.app"; - /** A provisioned merchant: a merchant-bound client plus its identifiers. */ public static final class TestMerchant { public final Gr4vy client; @@ -229,7 +227,7 @@ private static void deleteCreatedMerchantAccounts() { private static void deleteMerchantAccount(String merchantAccountId, String privateKey) { try { HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(API_BASE_URL + "/merchant-accounts/" + .uri(URI.create(Checkout.API_BASE_URL + "/merchant-accounts/" + URLEncoder.encode(merchantAccountId, StandardCharsets.UTF_8))) .timeout(Duration.ofSeconds(30)) .header("Authorization", "Bearer " + Auth.getToken(privateKey))