From 2be790abe28778b817b842a224b8823c3da106f9 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 11 Sep 2025 12:39:16 +0200 Subject: [PATCH 01/18] new classes for accounts --- .../sdk/domain/accounts/EntityStatus.java | 37 +++++++++++++++++++ .../dto/OrganizationalResourceGroup.java | 18 +++++++++ ...ertOrganizationalResourceGroupRequest.java | 3 ++ 3 files changed, 58 insertions(+) create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/EntityStatus.java create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/dto/OrganizationalResourceGroup.java create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/requests/UpsertOrganizationalResourceGroupRequest.java diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/EntityStatus.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/EntityStatus.java new file mode 100644 index 00000000..ba5f8807 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/EntityStatus.java @@ -0,0 +1,37 @@ +package com.yanchware.fractal.sdk.domain.accounts; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public enum EntityStatus { + UNKNOWN("Unknown"), + ACTIVE("Active"), + DELETING("Deleting"); + + private final String value; + + EntityStatus(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return value; + } + + @JsonCreator + public static EntityStatus fromString(String s) { + if (s == null) return null; + for (var e : values()) { + if (e.value.equalsIgnoreCase(s) || e.name().equalsIgnoreCase(s)) { + return e; + } + } + throw new IllegalArgumentException("Invalid EntityStatus: " + s); + } +} \ No newline at end of file diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/dto/OrganizationalResourceGroup.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/dto/OrganizationalResourceGroup.java new file mode 100644 index 00000000..9d421cc8 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/dto/OrganizationalResourceGroup.java @@ -0,0 +1,18 @@ +package com.yanchware.fractal.sdk.domain.accounts.services.dto; + +import com.yanchware.fractal.sdk.domain.accounts.EntityStatus; +import com.yanchware.fractal.sdk.domain.values.ResourceGroupId; +import java.util.Collection; + +public record OrganizationalResourceGroup( + ResourceGroupId Id, + String DisplayName, + String Description, + EntityStatus Status, + String Icon, + Collection Members, + Collection TeamsIds, + Collection ManagersIds, + Collection Livesystems, + Collection Fractals +) {} \ No newline at end of file diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/requests/UpsertOrganizationalResourceGroupRequest.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/requests/UpsertOrganizationalResourceGroupRequest.java new file mode 100644 index 00000000..bab48fc5 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/requests/UpsertOrganizationalResourceGroupRequest.java @@ -0,0 +1,3 @@ +package com.yanchware.fractal.sdk.domain.accounts.service.requests; + +public record UpsertOrganizationalResourceGroupRequest(String DisplayName, String Description, String Icon){ } \ No newline at end of file From 009db3d9466e80c8d159e2a9a8cec7b6828316af Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 11 Sep 2025 15:48:34 +0200 Subject: [PATCH 02/18] Added accounts aggregate, factory, service (implemented as REST service) and DTOs --- .../com/yanchware/fractal/sdk/Automaton.java | 10 +++ .../fractal/sdk/configuration/Constants.java | 1 + .../configuration/EnvVarSdkConfiguration.java | 13 ++++ .../sdk/configuration/SdkConfiguration.java | 7 +++ .../sdk/domain/accounts/AccountAggregate.java | 23 +++++++ .../sdk/domain/accounts/AccountsFactory.java | 49 +++++++++++++++ .../accounts/service/AccountsService.java | 16 +++++ .../accounts/service/RestAccountsService.java | 62 +++++++++++++++++++ ...ertOrganizationalResourceGroupRequest.java | 2 +- .../UpsertPersonalResourceGroupRequest.java | 4 ++ .../OrganizationalResourceGroupResponse.java} | 4 +- .../dtos/PersonalResourceGroupResponse.java | 16 +++++ .../sdk/utils/LocalSdkConfiguration.java | 4 ++ 13 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java rename fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/{services/requests => service/commands}/UpsertOrganizationalResourceGroupRequest.java (62%) create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/commands/UpsertPersonalResourceGroupRequest.java rename fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/{services/dto/OrganizationalResourceGroup.java => service/dtos/OrganizationalResourceGroupResponse.java} (79%) create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/dtos/PersonalResourceGroupResponse.java diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java index 6ccf31a2..57572be0 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java @@ -4,6 +4,7 @@ import com.yanchware.fractal.sdk.configuration.EnvVarSdkConfiguration; import com.yanchware.fractal.sdk.configuration.SdkConfiguration; import com.yanchware.fractal.sdk.configuration.instantiation.InstantiationConfiguration; +import com.yanchware.fractal.sdk.domain.accounts.AccountsFactory; import com.yanchware.fractal.sdk.domain.blueprint.BlueprintFactory; import com.yanchware.fractal.sdk.domain.environment.EnvironmentAggregate; import com.yanchware.fractal.sdk.domain.environment.EnvironmentIdValue; @@ -40,6 +41,7 @@ public class Automaton { private static BlueprintFactory blueprintFactory; private static LiveSystemsFactory liveSystemFactory; private static EnvironmentsFactory environmentsFactory; + private static AccountsFactory accountsFactory; private static RetryRegistry serviceRetryRegistry; private Automaton(HttpClient httpClient, SdkConfiguration sdkConfiguration) { @@ -47,6 +49,7 @@ private Automaton(HttpClient httpClient, SdkConfiguration sdkConfiguration) { Automaton.blueprintFactory = new BlueprintFactory(httpClient, sdkConfiguration, Automaton.serviceRetryRegistry); Automaton.liveSystemFactory = new LiveSystemsFactory(httpClient, sdkConfiguration, Automaton.serviceRetryRegistry); Automaton.environmentsFactory = new EnvironmentsFactory(httpClient, sdkConfiguration, Automaton.serviceRetryRegistry); + Automaton.accountsFactory = new AccountsFactory(httpClient, sdkConfiguration, Automaton.serviceRetryRegistry); } /** @@ -80,6 +83,13 @@ public LiveSystemsFactory.LiveSystemBuilder getLiveSystemBuilder() { return liveSystemFactory.builder(); } + /** + * Get builder for Account Aggregate + * + * @retun + */ + public AccountsFactory.AccountBuilder getAccountBuilder() {return accountsFactory.builder();} + /** * Instantiates the given environment. * diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/Constants.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/Constants.java index abbb62db..e9080216 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/Constants.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/Constants.java @@ -4,6 +4,7 @@ public class Constants { public static final String LIVESYSTEM_ENDPOINT_KEY = "LIVESYSTEM_ENDPOINT"; public static final String BLUEPRINT_ENDPOINT_KEY = "BLUEPRINT_ENDPOINT"; public static final String ENVIRONMENTS_ENDPOINT_KEY = "ENVIRONMENTS_ENDPOINT"; + public static final String ACCOUNTS_ENDPOINT_KEY = "ACCOUNTS_ENDPOINT"; public static final String GIT_COMMIT_ID_KEY = "commitId"; public static final String X_CLIENT_ID_HEADER = "X-ClientID"; diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java index 85a07fb2..f6db5e8e 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java @@ -29,6 +29,10 @@ public class EnvVarSdkConfiguration implements SdkConfiguration { */ public final URI DEFAULT_ENVIRONMENTS_ENDPOINT; + /** + * The default URI for the accounts endpoint. + */ + public final URI DEFAULT_ACCOUNTS_ENDPOINT; /** * Constructs an instance of {@link EnvVarSdkConfiguration}. * @@ -38,6 +42,7 @@ public EnvVarSdkConfiguration() throws URISyntaxException { DEFAULT_LIVESYSTEM_ENDPOINT = new URI("https://api.fractal.cloud/livesystems"); DEFAULT_BLUEPRINT_ENDPOINT = new URI("https://api.fractal.cloud/blueprints"); DEFAULT_ENVIRONMENTS_ENDPOINT = new URI("https://api.fractal.cloud/environments"); + DEFAULT_ACCOUNTS_ENDPOINT = new URI("https://api.fractal.cloud/accounts"); } /** @@ -129,6 +134,14 @@ public URI getEnvironmentsEndpoint() { return checkAndReturnUri(ENVIRONMENTS_ENDPOINT_KEY, DEFAULT_ENVIRONMENTS_ENDPOINT); } + /** + * Gets the URI of the accounts endpoint from environment variables or default. + * @return the URI of the accounts endpoint + */ + @Override + public URI getAccountsEndpoint(){ + return checkAndReturnUri(ACCOUNTS_ENDPOINT_KEY, DEFAULT_ACCOUNTS_ENDPOINT); + } /** * Gets the AWS Access Key ID, part of the temporary credentials of an AWS role, from environment variables. * The environment variable key is {@value com.yanchware.fractal.sdk.configuration.Constants#AWS_ACCESS_KEY_ID_KEY}. diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/SdkConfiguration.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/SdkConfiguration.java index 0d6218ac..b550f8f9 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/SdkConfiguration.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/SdkConfiguration.java @@ -56,6 +56,13 @@ public interface SdkConfiguration { */ URI getEnvironmentsEndpoint(); + /** + * Gets the URI of the accounts endpoint. + * + * @return the URI of the accounts endpoint + */ + URI getAccountsEndpoint(); + /** * Gets the AWS Access Key ID. * diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java new file mode 100644 index 00000000..514f9108 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java @@ -0,0 +1,23 @@ +package com.yanchware.fractal.sdk.domain.accounts; + +import com.yanchware.fractal.sdk.domain.accounts.service.AccountsService; +import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; + +public class AccountAggregate { + + private final AccountsService service; + + public AccountAggregate(RestAccountsService restAccountsService) { + this.service = restAccountsService; + } + + public void createOrUpdateResourceGroup(String Shortname, String DisplayName) throws InstantiatorException { + service.upsertPersonalResourceGroup(Shortname, DisplayName); + } + + public void setAccount(AccountAggregate accountAggregate) { + + } +} diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java new file mode 100644 index 00000000..658381e1 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java @@ -0,0 +1,49 @@ +package com.yanchware.fractal.sdk.domain.accounts; + +import com.yanchware.fractal.sdk.configuration.SdkConfiguration; +import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; +import com.yanchware.fractal.sdk.domain.environment.EnvironmentsFactory; +import io.github.resilience4j.retry.RetryRegistry; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.net.http.HttpClient; + +@Slf4j +@AllArgsConstructor +public class AccountsFactory { + private final HttpClient client; + private final SdkConfiguration sdkConfiguration; + private final RetryRegistry retryRegistry; + + public AccountBuilder builder(){ + return new AccountBuilder(client, sdkConfiguration, retryRegistry); + } + + public static class AccountBuilder{ + private final AccountAggregate aggregate; + private final AccountBuilder builder; + + public AccountBuilder(HttpClient client, SdkConfiguration sdkConfiguration, RetryRegistry retryRegistry) { + aggregate = create(client, sdkConfiguration, retryRegistry); + builder = getBuilder(); + } + + protected AccountAggregate create(HttpClient client, SdkConfiguration sdkConfiguration, RetryRegistry retryRegistry) { + return new AccountAggregate(new RestAccountsService(client, sdkConfiguration, retryRegistry)); + } + protected AccountsFactory.AccountBuilder getBuilder() { + return this; + } + + public AccountBuilder withManagementEnvironment(AccountAggregate accountAggregate) { + aggregate.setAccount(accountAggregate); + return builder; + } + + + public AccountAggregate build() { + return aggregate; + } + } +} diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java new file mode 100644 index 00000000..75102805 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java @@ -0,0 +1,16 @@ +package com.yanchware.fractal.sdk.domain.accounts.service; + +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.OrganizationalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; + +import java.util.UUID; + +public interface AccountsService { + OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup( + UUID organizationId, + String shortName) throws InstantiatorException; + + PersonalResourceGroupResponse upsertPersonalResourceGroup(String shortName, String displayName) throws InstantiatorException; + +} \ No newline at end of file diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java new file mode 100644 index 00000000..7b93333f --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java @@ -0,0 +1,62 @@ +package com.yanchware.fractal.sdk.domain.accounts.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.yanchware.fractal.sdk.configuration.SdkConfiguration; +import com.yanchware.fractal.sdk.domain.Service; +import com.yanchware.fractal.sdk.domain.accounts.service.commands.UpsertPersonalResourceGroupRequest; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.OrganizationalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.environment.EnvironmentIdValue; +import com.yanchware.fractal.sdk.domain.environment.service.dtos.EnvironmentResponse; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; +import com.yanchware.fractal.sdk.domain.values.ResourceGroupId; +import com.yanchware.fractal.sdk.utils.HttpUtils; +import io.github.resilience4j.retry.RetryRegistry; + +import java.net.URI; +import java.net.http.HttpClient; +import java.util.UUID; + +import static com.yanchware.fractal.sdk.utils.SerializationUtils.serialize; + + +public class RestAccountsService extends Service implements AccountsService { + public RestAccountsService(HttpClient client, SdkConfiguration sdkConfiguration, RetryRegistry retryRegistry) { + super(client, sdkConfiguration, retryRegistry); + } + + @Override + public OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup(UUID organizationId, String shortName) throws InstantiatorException { + return null; + } + + @Override + public PersonalResourceGroupResponse upsertPersonalResourceGroup(String shortName, String displayName) throws InstantiatorException { + return executeRequestWithRetries( + "", + "upsertPersonalResourceGroup", + client, + retryRegistry, + HttpUtils.buildPostRequest( + getResourceGroupsUri(shortName), + sdkConfiguration, + serializeSafely(new UpsertPersonalResourceGroupRequest(displayName, null, null))), + new int[]{200}, + PersonalResourceGroupResponse.class); + } + + private String serializeSafely(Object command) throws InstantiatorException { + try { + return serialize(command); + } catch (JsonProcessingException e) { + throw new InstantiatorException("Error serializing command because of JsonProcessing error", e); + } + } + + private URI getResourceGroupsUri(String shortName) { + var path = String.format("%s/%s/", + sdkConfiguration.getAccountsEndpoint(), + "resourceGroups"); + return URI.create(path + shortName); + } +} diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/requests/UpsertOrganizationalResourceGroupRequest.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/commands/UpsertOrganizationalResourceGroupRequest.java similarity index 62% rename from fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/requests/UpsertOrganizationalResourceGroupRequest.java rename to fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/commands/UpsertOrganizationalResourceGroupRequest.java index bab48fc5..6a5a802a 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/requests/UpsertOrganizationalResourceGroupRequest.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/commands/UpsertOrganizationalResourceGroupRequest.java @@ -1,3 +1,3 @@ -package com.yanchware.fractal.sdk.domain.accounts.service.requests; +package com.yanchware.fractal.sdk.domain.accounts.service.commands; public record UpsertOrganizationalResourceGroupRequest(String DisplayName, String Description, String Icon){ } \ No newline at end of file diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/commands/UpsertPersonalResourceGroupRequest.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/commands/UpsertPersonalResourceGroupRequest.java new file mode 100644 index 00000000..8666a208 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/commands/UpsertPersonalResourceGroupRequest.java @@ -0,0 +1,4 @@ +package com.yanchware.fractal.sdk.domain.accounts.service.commands; + +public record UpsertPersonalResourceGroupRequest(String DisplayName, String Description, String Icon) { +} diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/dto/OrganizationalResourceGroup.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/dtos/OrganizationalResourceGroupResponse.java similarity index 79% rename from fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/dto/OrganizationalResourceGroup.java rename to fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/dtos/OrganizationalResourceGroupResponse.java index 9d421cc8..15fa5a2b 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/services/dto/OrganizationalResourceGroup.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/dtos/OrganizationalResourceGroupResponse.java @@ -1,10 +1,10 @@ -package com.yanchware.fractal.sdk.domain.accounts.services.dto; +package com.yanchware.fractal.sdk.domain.accounts.service.dtos; import com.yanchware.fractal.sdk.domain.accounts.EntityStatus; import com.yanchware.fractal.sdk.domain.values.ResourceGroupId; import java.util.Collection; -public record OrganizationalResourceGroup( +public record OrganizationalResourceGroupResponse( ResourceGroupId Id, String DisplayName, String Description, diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/dtos/PersonalResourceGroupResponse.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/dtos/PersonalResourceGroupResponse.java new file mode 100644 index 00000000..07e68530 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/dtos/PersonalResourceGroupResponse.java @@ -0,0 +1,16 @@ +package com.yanchware.fractal.sdk.domain.accounts.service.dtos; + +import com.yanchware.fractal.sdk.domain.accounts.EntityStatus; +import com.yanchware.fractal.sdk.domain.values.ResourceGroupId; + +import java.util.Collection; + +public record PersonalResourceGroupResponse( + ResourceGroupId Id, + String DisplayName, + String Description, + EntityStatus Status, + String Icon, + Collection Livesystems, + Collection Fractals +) {} diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/utils/LocalSdkConfiguration.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/utils/LocalSdkConfiguration.java index c4314f58..06babad5 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/utils/LocalSdkConfiguration.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/utils/LocalSdkConfiguration.java @@ -49,7 +49,11 @@ public URI getLiveSystemEndpoint() { @Override public URI getEnvironmentsEndpoint() { return new URI(httpBaseUrl + "/environments"); } + @SneakyThrows @Override + public URI getAccountsEndpoint() { return new URI(httpBaseUrl + "/accounts");} + + @Override public String getAwsAccessKeyId() { return "xxx"; } From 913d2970e18ee54a00c39ad3b09c4fb833deaa7f Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 11 Sep 2025 16:20:49 +0200 Subject: [PATCH 03/18] Fixed the factory --- .../fractal/sdk/domain/accounts/AccountAggregate.java | 6 +++++- .../fractal/sdk/domain/accounts/AccountsFactory.java | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java index 514f9108..c1b9160b 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java @@ -2,9 +2,13 @@ import com.yanchware.fractal.sdk.domain.accounts.service.AccountsService; import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; -import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; +import lombok.AccessLevel; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +@Slf4j +@Setter(AccessLevel.PROTECTED) public class AccountAggregate { private final AccountsService service; diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java index 658381e1..c8f33a31 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java @@ -2,7 +2,7 @@ import com.yanchware.fractal.sdk.configuration.SdkConfiguration; import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; -import com.yanchware.fractal.sdk.domain.environment.EnvironmentsFactory; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; import io.github.resilience4j.retry.RetryRegistry; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -36,8 +36,8 @@ protected AccountsFactory.AccountBuilder getBuilder() { return this; } - public AccountBuilder withManagementEnvironment(AccountAggregate accountAggregate) { - aggregate.setAccount(accountAggregate); + public AccountBuilder withResourceGroup(String shortName, String displayName) throws InstantiatorException { + aggregate.createOrUpdateResourceGroup(shortName,displayName); return builder; } From f4902952da03eae5dda02b9449fe5c279bf01e7a Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 16 Sep 2025 16:52:24 +0200 Subject: [PATCH 04/18] Implemented aggregate plus factory --- .../com/yanchware/fractal/sdk/Automaton.java | 12 ++++- .../sdk/domain/accounts/AccountAggregate.java | 51 +++++++++++++++++-- .../sdk/domain/accounts/AccountsFactory.java | 15 ++---- .../accounts/service/AccountsService.java | 4 +- .../accounts/service/RestAccountsService.java | 19 +++++-- 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java index 57572be0..4f482830 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java @@ -4,6 +4,7 @@ import com.yanchware.fractal.sdk.configuration.EnvVarSdkConfiguration; import com.yanchware.fractal.sdk.configuration.SdkConfiguration; import com.yanchware.fractal.sdk.configuration.instantiation.InstantiationConfiguration; +import com.yanchware.fractal.sdk.domain.accounts.AccountAggregate; import com.yanchware.fractal.sdk.domain.accounts.AccountsFactory; import com.yanchware.fractal.sdk.domain.blueprint.BlueprintFactory; import com.yanchware.fractal.sdk.domain.environment.EnvironmentAggregate; @@ -88,7 +89,9 @@ public LiveSystemsFactory.LiveSystemBuilder getLiveSystemBuilder() { * * @retun */ - public AccountsFactory.AccountBuilder getAccountBuilder() {return accountsFactory.builder();} + public AccountsFactory.AccountBuilder getAccountBuilder() { + return accountsFactory.builder(); + } /** * Instantiates the given environment. @@ -100,7 +103,12 @@ public void instantiate(EnvironmentAggregate environment) throws InstantiatorExc instantiateEnvironment(environment); } - /** + public void instantiate(AccountAggregate accounts) throws InstantiatorException { + accounts.createOrUpdate(); + } + + + /** * Instantiates the given list of live systems. * * @param liveSystems the list of live systems to be instantiated diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java index c1b9160b..0a5abc82 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java @@ -2,26 +2,71 @@ import com.yanchware.fractal.sdk.domain.accounts.service.AccountsService; import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; import lombok.AccessLevel; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; +import java.util.List; + @Slf4j @Setter(AccessLevel.PROTECTED) public class AccountAggregate { private final AccountsService service; + public record PersonalResourceGroup(String shortName, String displayName) { + } + + private final List personalResourceGroups = new ArrayList<>(); + public AccountAggregate(RestAccountsService restAccountsService) { this.service = restAccountsService; } - public void createOrUpdateResourceGroup(String Shortname, String DisplayName) throws InstantiatorException { - service.upsertPersonalResourceGroup(Shortname, DisplayName); + public void addPersonalResourceGroup(String shortName, String displayName) { + int idx = -1; + for (int i = 0; i < personalResourceGroups.size(); i++) { + if (personalResourceGroups.get(i).shortName().equals(shortName)) { + idx = i; + break; + } + } + if (idx >= 0) { + var existing = personalResourceGroups.get(idx); + if (!existing.displayName().equals(displayName)) { + personalResourceGroups.set(idx, new PersonalResourceGroup(shortName, displayName)); + } + return; + } + personalResourceGroups.add(new PersonalResourceGroup(shortName, displayName)); } - public void setAccount(AccountAggregate accountAggregate) { + public void createOrUpdate() throws InstantiatorException { + if (personalResourceGroups.isEmpty()) { + log.info("No personal resource groups to manage."); + return; + } + + int created = 0; + for (var rg : personalResourceGroups) { + var shortName = rg.shortName(); + var displayName = rg.displayName(); + + PersonalResourceGroupResponse existing = service.getByShortName(shortName); + + if (existing == null) { + log.info("Creating Personal Resource Group [shortName: '{}', displayName: '{}']", shortName, displayName); + var response = service.upsertPersonalResourceGroup(shortName, displayName); + log.debug("Upserted Personal Resource Group id: {}", response.Id()); + created++; + } else { + log.info("Personal Resource Group [shortName: '{}'] already exists. Skipping.", shortName); + } + } + log.info("Created {} new Personal Resource Group(s).", created); } } diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java index c8f33a31..03733566 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java @@ -2,7 +2,6 @@ import com.yanchware.fractal.sdk.configuration.SdkConfiguration; import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; -import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; import io.github.resilience4j.retry.RetryRegistry; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,11 +15,11 @@ public class AccountsFactory { private final SdkConfiguration sdkConfiguration; private final RetryRegistry retryRegistry; - public AccountBuilder builder(){ + public AccountBuilder builder() { return new AccountBuilder(client, sdkConfiguration, retryRegistry); } - public static class AccountBuilder{ + public static class AccountBuilder { private final AccountAggregate aggregate; private final AccountBuilder builder; @@ -32,18 +31,14 @@ public AccountBuilder(HttpClient client, SdkConfiguration sdkConfiguration, Retr protected AccountAggregate create(HttpClient client, SdkConfiguration sdkConfiguration, RetryRegistry retryRegistry) { return new AccountAggregate(new RestAccountsService(client, sdkConfiguration, retryRegistry)); } + protected AccountsFactory.AccountBuilder getBuilder() { return this; } - public AccountBuilder withResourceGroup(String shortName, String displayName) throws InstantiatorException { - aggregate.createOrUpdateResourceGroup(shortName,displayName); + public AccountBuilder withResourceGroup(String shortName, String displayName) { + aggregate.addPersonalResourceGroup(shortName, displayName); return builder; } - - - public AccountAggregate build() { - return aggregate; - } } } diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java index 75102805..20fd2c53 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java @@ -7,10 +7,10 @@ import java.util.UUID; public interface AccountsService { - OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup( + OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup( UUID organizationId, String shortName) throws InstantiatorException; PersonalResourceGroupResponse upsertPersonalResourceGroup(String shortName, String displayName) throws InstantiatorException; - + PersonalResourceGroupResponse getByShortName(String shortName) throws InstantiatorException; } \ No newline at end of file diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java index 7b93333f..ddfc603d 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java @@ -6,10 +6,7 @@ import com.yanchware.fractal.sdk.domain.accounts.service.commands.UpsertPersonalResourceGroupRequest; import com.yanchware.fractal.sdk.domain.accounts.service.dtos.OrganizationalResourceGroupResponse; import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; -import com.yanchware.fractal.sdk.domain.environment.EnvironmentIdValue; -import com.yanchware.fractal.sdk.domain.environment.service.dtos.EnvironmentResponse; import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; -import com.yanchware.fractal.sdk.domain.values.ResourceGroupId; import com.yanchware.fractal.sdk.utils.HttpUtils; import io.github.resilience4j.retry.RetryRegistry; @@ -33,7 +30,7 @@ public OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup(UUI @Override public PersonalResourceGroupResponse upsertPersonalResourceGroup(String shortName, String displayName) throws InstantiatorException { return executeRequestWithRetries( - "", + "upsertPersonalResourceGroup", "upsertPersonalResourceGroup", client, retryRegistry, @@ -45,6 +42,20 @@ public PersonalResourceGroupResponse upsertPersonalResourceGroup(String shortNam PersonalResourceGroupResponse.class); } + @Override + public PersonalResourceGroupResponse getByShortName(String shortName) throws InstantiatorException { + return executeRequestWithRetries( + "upsertPersonalResourceGroup", + "upsertPersonalResourceGroup", + client, + retryRegistry, + HttpUtils.buildGetRequest( + getResourceGroupsUri(shortName), + sdkConfiguration), + new int[]{200}, + PersonalResourceGroupResponse.class); + } + private String serializeSafely(Object command) throws InstantiatorException { try { return serialize(command); From 01dfaed3146f764734d7fb70e97f0fdfac2ba64b Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 16 Sep 2025 16:52:47 +0200 Subject: [PATCH 05/18] Unit tests for Account Aggregate --- .../domain/account/AccountAggregateTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java new file mode 100644 index 00000000..693b5e22 --- /dev/null +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java @@ -0,0 +1,79 @@ +package com.yanchware.fractal.sdk.domain.account; + +import com.yanchware.fractal.sdk.domain.accounts.AccountAggregate; +import com.yanchware.fractal.sdk.domain.accounts.service.AccountsService; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; +import com.yanchware.fractal.sdk.domain.values.ResourceGroupId; +import com.yanchware.fractal.sdk.domain.accounts.EntityStatus; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +import static com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +class AccountAggregateTest { + private static final String SHORT_NAME = "test-rg"; + private static final String DISPLAY_NAME = "Test Resource Group"; + + @Test + void createsPersonalResourceGroup_when_NotExists() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new AccountAggregate(mockService); + + aggregate.addPersonalResourceGroup(SHORT_NAME, DISPLAY_NAME); + + when(mockService.getByShortName(eq(SHORT_NAME))).thenReturn(null); + var createdResponse = new PersonalResourceGroupResponse( + ResourceGroupId.fromString(String.format("Personal/%s/%s", UUID.randomUUID(), SHORT_NAME)), + DISPLAY_NAME, + null, + EntityStatus.ACTIVE, + null, + List.of(), + List.of() + ); + when(mockService.upsertPersonalResourceGroup(eq(SHORT_NAME), eq(DISPLAY_NAME))) + .thenReturn(createdResponse); + + // Act + aggregate.createOrUpdate(); + + // Assert + verify(mockService, times(1)).getByShortName(eq(SHORT_NAME)); + verify(mockService, times(1)).upsertPersonalResourceGroup(eq(SHORT_NAME), eq(DISPLAY_NAME)); + verifyNoMoreInteractions(mockService); + } + + @Test + void skipsUpsert_when_AlreadyExists() throws InstantiatorException { + // Arrange + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new AccountAggregate(mockService); + + var shortName = "short-name-rg"; + var displayName = "Display Name RG"; + aggregate.addPersonalResourceGroup(shortName, displayName); + + var existingResponse = new PersonalResourceGroupResponse( + ResourceGroupId.fromString(String.format("Personal/%s/%s", UUID.randomUUID(), shortName)), + "Existing Bob", + null, + EntityStatus.ACTIVE, + null, + List.of(), + List.of() + ); + + when(mockService.getByShortName(eq(shortName))).thenReturn(existingResponse); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getByShortName(eq(shortName)); + verify(mockService, never()).upsertPersonalResourceGroup(anyString(), anyString()); + verifyNoMoreInteractions(mockService); + } +} \ No newline at end of file From fc0854ac96bf43425cbd29745313b0f031ce241e Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 16 Sep 2025 16:56:23 +0200 Subject: [PATCH 06/18] Cleanup unit tests warning --- .../sdk/domain/account/AccountAggregateTest.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java index 693b5e22..1aa89d32 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java @@ -1,7 +1,6 @@ package com.yanchware.fractal.sdk.domain.account; import com.yanchware.fractal.sdk.domain.accounts.AccountAggregate; -import com.yanchware.fractal.sdk.domain.accounts.service.AccountsService; import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; import com.yanchware.fractal.sdk.domain.values.ResourceGroupId; @@ -11,9 +10,14 @@ import java.util.List; import java.util.UUID; -import static com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse.*; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; class AccountAggregateTest { private static final String SHORT_NAME = "test-rg"; From 330f57b32449b73c67f3e8ef13ebb66e0caac66f Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 09:57:38 +0200 Subject: [PATCH 07/18] OrganizationAggregate implemented with service and factory --- .../com/yanchware/fractal/sdk/Automaton.java | 33 +++++++- .../sdk/domain/accounts/AccountAggregate.java | 2 +- .../accounts/OrganizationAggregate.java | 79 +++++++++++++++++++ .../domain/accounts/OrganizationFactory.java | 49 ++++++++++++ .../accounts/service/AccountsService.java | 9 ++- .../accounts/service/RestAccountsService.java | 65 +++++++++++---- .../domain/account/AccountAggregateTest.java | 8 +- 7 files changed, 219 insertions(+), 26 deletions(-) create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/OrganizationAggregate.java create mode 100644 fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/OrganizationFactory.java diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java index 4f482830..2c2d3b0d 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/Automaton.java @@ -6,6 +6,8 @@ import com.yanchware.fractal.sdk.configuration.instantiation.InstantiationConfiguration; import com.yanchware.fractal.sdk.domain.accounts.AccountAggregate; import com.yanchware.fractal.sdk.domain.accounts.AccountsFactory; +import com.yanchware.fractal.sdk.domain.accounts.OrganizationAggregate; +import com.yanchware.fractal.sdk.domain.accounts.OrganizationFactory; import com.yanchware.fractal.sdk.domain.blueprint.BlueprintFactory; import com.yanchware.fractal.sdk.domain.environment.EnvironmentAggregate; import com.yanchware.fractal.sdk.domain.environment.EnvironmentIdValue; @@ -43,6 +45,7 @@ public class Automaton { private static LiveSystemsFactory liveSystemFactory; private static EnvironmentsFactory environmentsFactory; private static AccountsFactory accountsFactory; + private static OrganizationFactory organizationFactory; private static RetryRegistry serviceRetryRegistry; private Automaton(HttpClient httpClient, SdkConfiguration sdkConfiguration) { @@ -51,6 +54,7 @@ private Automaton(HttpClient httpClient, SdkConfiguration sdkConfiguration) { Automaton.liveSystemFactory = new LiveSystemsFactory(httpClient, sdkConfiguration, Automaton.serviceRetryRegistry); Automaton.environmentsFactory = new EnvironmentsFactory(httpClient, sdkConfiguration, Automaton.serviceRetryRegistry); Automaton.accountsFactory = new AccountsFactory(httpClient, sdkConfiguration, Automaton.serviceRetryRegistry); + Automaton.organizationFactory = new OrganizationFactory(httpClient, sdkConfiguration, Automaton.serviceRetryRegistry); } /** @@ -87,13 +91,22 @@ public LiveSystemsFactory.LiveSystemBuilder getLiveSystemBuilder() { /** * Get builder for Account Aggregate * - * @retun + * @return */ public AccountsFactory.AccountBuilder getAccountBuilder() { return accountsFactory.builder(); } - /** + /** + * Get builder for Organization Aggregate + * + * @return + */ + public OrganizationFactory.OrganizationBuilder getOrganizationBuilder() { + return organizationFactory.builder(); + } + + /** * Instantiates the given environment. * * @param environment the environment to be instantiated @@ -103,10 +116,26 @@ public void instantiate(EnvironmentAggregate environment) throws InstantiatorExc instantiateEnvironment(environment); } + /** + * Instantiates the given account. + * + * @param accounts the account to be instantiated + * @throws InstantiatorException if an error occurs + */ public void instantiate(AccountAggregate accounts) throws InstantiatorException { accounts.createOrUpdate(); } + /** + * Instantiates the given organization aggregate by reconciling organizational Resource Groups. + * + * @param organization the organization to be instantiated + * @throws InstantiatorException if an error occurs + */ + public void instantiate(OrganizationAggregate organization) throws InstantiatorException { + organization.createOrUpdate(); + } + /** * Instantiates the given list of live systems. diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java index 0a5abc82..0f9c6b44 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountAggregate.java @@ -56,7 +56,7 @@ public void createOrUpdate() throws InstantiatorException { var shortName = rg.shortName(); var displayName = rg.displayName(); - PersonalResourceGroupResponse existing = service.getByShortName(shortName); + PersonalResourceGroupResponse existing = service.getPersonalResourceGroupByShortName(shortName); if (existing == null) { log.info("Creating Personal Resource Group [shortName: '{}', displayName: '{}']", shortName, displayName); diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/OrganizationAggregate.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/OrganizationAggregate.java new file mode 100644 index 00000000..fecd4ad6 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/OrganizationAggregate.java @@ -0,0 +1,79 @@ +package com.yanchware.fractal.sdk.domain.accounts; + +import com.yanchware.fractal.sdk.domain.accounts.service.AccountsService; +import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.OrganizationalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; +import lombok.AccessLevel; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Slf4j +@Setter(AccessLevel.PROTECTED) +public class OrganizationAggregate { + + private final AccountsService service; + + public record OrganizationalResourceGroup(UUID organizationId, String shortName, String displayName) {} + + private final List organizationalResourceGroups = new ArrayList<>(); + + public OrganizationAggregate(RestAccountsService restAccountsService) { + this.service = restAccountsService; + } + + public void addOrganizationalResourceGroup(UUID organizationId, String shortName, String displayName) { + int idx = -1; + for (int i = 0; i < organizationalResourceGroups.size(); i++) { + var item = organizationalResourceGroups.get(i); + if (item.organizationId().equals(organizationId) && item.shortName().equals(shortName)) { + idx = i; + break; + } + } + if (idx >= 0) { + var existing = organizationalResourceGroups.get(idx); + if (!existing.displayName().equals(displayName)) { + organizationalResourceGroups.set(idx, new OrganizationalResourceGroup(organizationId, shortName, displayName)); + } + return; + } + organizationalResourceGroups.add(new OrganizationalResourceGroup(organizationId, shortName, displayName)); + } + + public void createOrUpdate() throws InstantiatorException { + if (organizationalResourceGroups.isEmpty()) { + log.info("No organizational resource groups to manage."); + return; + } + + int created = 0; + for (var rg : organizationalResourceGroups) { + var organizationId = rg.organizationId(); + var shortName = rg.shortName(); + var displayName = rg.displayName(); + + OrganizationalResourceGroupResponse existing = + service.getOrganizationalResourceGroupByShortName(organizationId, shortName); + + if (existing == null) { + log.info("Creating Organizational Resource Group [organizationId: '{}', shortName: '{}', displayName: '{}']", + organizationId, shortName, displayName); + + var response = service.upsertOrganizationalResourceGroup(organizationId, shortName, displayName); + if (response != null) { + log.debug("Upserted Organizational Resource Group id: {}", response.Id()); + } + created++; + } else { + log.info("Organizational Resource Group [organizationId: '{}', shortName: '{}'] already exists. Skipping.", + organizationId, shortName); + } + } + log.info("Created {} new Organizational Resource Group(s).", created); + } +} diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/OrganizationFactory.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/OrganizationFactory.java new file mode 100644 index 00000000..f6a55f20 --- /dev/null +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/OrganizationFactory.java @@ -0,0 +1,49 @@ +package com.yanchware.fractal.sdk.domain.accounts; + +import com.yanchware.fractal.sdk.configuration.SdkConfiguration; +import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; +import io.github.resilience4j.retry.RetryRegistry; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.net.http.HttpClient; +import java.util.UUID; + +@Slf4j +@AllArgsConstructor +public class OrganizationFactory { + private final HttpClient client; + private final SdkConfiguration sdkConfiguration; + private final RetryRegistry retryRegistry; + + public OrganizationBuilder builder() { + return new OrganizationBuilder(client, sdkConfiguration, retryRegistry); + } + + public static class OrganizationBuilder { + private final OrganizationAggregate aggregate; + private final OrganizationBuilder builder; + + public OrganizationBuilder(HttpClient client, SdkConfiguration sdkConfiguration, RetryRegistry retryRegistry) { + aggregate = create(client, sdkConfiguration, retryRegistry); + builder = getBuilder(); + } + + protected OrganizationAggregate create(HttpClient client, SdkConfiguration sdkConfiguration, RetryRegistry retryRegistry) { + return new OrganizationAggregate(new RestAccountsService(client, sdkConfiguration, retryRegistry)); + } + + protected OrganizationBuilder getBuilder() { + return this; + } + + public OrganizationBuilder withResourceGroup(UUID organizationId, String shortName, String displayName) { + aggregate.addOrganizationalResourceGroup(organizationId, shortName, displayName); + return builder; + } + + public OrganizationAggregate build() { + return aggregate; + } + } +} diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java index 20fd2c53..3da5eaf1 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/AccountsService.java @@ -7,10 +7,11 @@ import java.util.UUID; public interface AccountsService { - OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup( - UUID organizationId, - String shortName) throws InstantiatorException; + OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup(UUID organizationId, String shortName, String displayName) throws InstantiatorException; + + OrganizationalResourceGroupResponse getOrganizationalResourceGroupByShortName(UUID organizationId, String shortName) throws InstantiatorException; PersonalResourceGroupResponse upsertPersonalResourceGroup(String shortName, String displayName) throws InstantiatorException; - PersonalResourceGroupResponse getByShortName(String shortName) throws InstantiatorException; + + PersonalResourceGroupResponse getPersonalResourceGroupByShortName(String shortName) throws InstantiatorException; } \ No newline at end of file diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java index ddfc603d..e39f9b40 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java @@ -23,19 +23,44 @@ public RestAccountsService(HttpClient client, SdkConfiguration sdkConfiguration, } @Override - public OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup(UUID organizationId, String shortName) throws InstantiatorException { - return null; + public OrganizationalResourceGroupResponse upsertOrganizationalResourceGroup(UUID organizationId, String shortName, String displayName) throws InstantiatorException { + return executeRequestWithRetries( + "upsertOrganizationalResourceGroup", + shortName, + client, + retryRegistry, + HttpUtils.buildPostRequest( + getOrganizationalResourceGroupsUri(organizationId,shortName), + sdkConfiguration, + serializeSafely(new UpsertPersonalResourceGroupRequest(displayName, null, null))), + new int[]{200}, + OrganizationalResourceGroupResponse.class); } + @Override + public OrganizationalResourceGroupResponse getOrganizationalResourceGroupByShortName(UUID organizationId, String shortName) throws InstantiatorException { + return executeRequestWithRetries( + "getOrganizationalResourceGroupByShortName", + organizationId + "/" + shortName, + client, + retryRegistry, + HttpUtils.buildGetRequest( + getOrganizationalResourceGroupsUri(organizationId, shortName), + sdkConfiguration), + new int[]{200}, + OrganizationalResourceGroupResponse.class); + } + + @Override public PersonalResourceGroupResponse upsertPersonalResourceGroup(String shortName, String displayName) throws InstantiatorException { return executeRequestWithRetries( "upsertPersonalResourceGroup", - "upsertPersonalResourceGroup", + shortName, client, retryRegistry, HttpUtils.buildPostRequest( - getResourceGroupsUri(shortName), + getPersonalResourceGroupsUri(shortName), sdkConfiguration, serializeSafely(new UpsertPersonalResourceGroupRequest(displayName, null, null))), new int[]{200}, @@ -43,19 +68,36 @@ public PersonalResourceGroupResponse upsertPersonalResourceGroup(String shortNam } @Override - public PersonalResourceGroupResponse getByShortName(String shortName) throws InstantiatorException { + public PersonalResourceGroupResponse getPersonalResourceGroupByShortName(String shortName) throws InstantiatorException { return executeRequestWithRetries( - "upsertPersonalResourceGroup", - "upsertPersonalResourceGroup", + "getPersonalResourceGroupByShortName", + shortName, client, retryRegistry, HttpUtils.buildGetRequest( - getResourceGroupsUri(shortName), + getPersonalResourceGroupsUri(shortName), sdkConfiguration), new int[]{200}, PersonalResourceGroupResponse.class); } + private URI getPersonalResourceGroupsUri(String shortName) { + var path = String.format("%s/%s/", + sdkConfiguration.getAccountsEndpoint(), + "accounts/resourcegroups"); // or your existing segment if already correct + return URI.create(path + shortName); + } + + private URI getOrganizationalResourceGroupsUri(UUID organizationId, String shortName) { + var base = String.format("%s/%s/%s/%s/", + sdkConfiguration.getAccountsEndpoint(), + "organizations", + organizationId, + "resourcegroups"); + return URI.create(base + shortName); + } + + private String serializeSafely(Object command) throws InstantiatorException { try { return serialize(command); @@ -63,11 +105,4 @@ private String serializeSafely(Object command) throws InstantiatorException { throw new InstantiatorException("Error serializing command because of JsonProcessing error", e); } } - - private URI getResourceGroupsUri(String shortName) { - var path = String.format("%s/%s/", - sdkConfiguration.getAccountsEndpoint(), - "resourceGroups"); - return URI.create(path + shortName); - } } diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java index 1aa89d32..a69df8e5 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java @@ -30,7 +30,7 @@ void createsPersonalResourceGroup_when_NotExists() throws InstantiatorException aggregate.addPersonalResourceGroup(SHORT_NAME, DISPLAY_NAME); - when(mockService.getByShortName(eq(SHORT_NAME))).thenReturn(null); + when(mockService.getPersonalResourceGroupByShortName(eq(SHORT_NAME))).thenReturn(null); var createdResponse = new PersonalResourceGroupResponse( ResourceGroupId.fromString(String.format("Personal/%s/%s", UUID.randomUUID(), SHORT_NAME)), DISPLAY_NAME, @@ -47,7 +47,7 @@ void createsPersonalResourceGroup_when_NotExists() throws InstantiatorException aggregate.createOrUpdate(); // Assert - verify(mockService, times(1)).getByShortName(eq(SHORT_NAME)); + verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(SHORT_NAME)); verify(mockService, times(1)).upsertPersonalResourceGroup(eq(SHORT_NAME), eq(DISPLAY_NAME)); verifyNoMoreInteractions(mockService); } @@ -72,11 +72,11 @@ void skipsUpsert_when_AlreadyExists() throws InstantiatorException { List.of() ); - when(mockService.getByShortName(eq(shortName))).thenReturn(existingResponse); + when(mockService.getPersonalResourceGroupByShortName(eq(shortName))).thenReturn(existingResponse); aggregate.createOrUpdate(); - verify(mockService, times(1)).getByShortName(eq(shortName)); + verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(shortName)); verify(mockService, never()).upsertPersonalResourceGroup(anyString(), anyString()); verifyNoMoreInteractions(mockService); } From f453402a671a2a63909d7a7ed786ffaa388cfe0b Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 10:11:37 +0200 Subject: [PATCH 08/18] Account aggregate tests --- .../domain/account/AccountAggregateTest.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java index a69df8e5..684b5c9f 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; @@ -80,4 +81,73 @@ void skipsUpsert_when_AlreadyExists() throws InstantiatorException { verify(mockService, never()).upsertPersonalResourceGroup(anyString(), anyString()); verifyNoMoreInteractions(mockService); } + + @Test + void updatesDisplayName_when_AddedTwiceWithDifferentDisplayName() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new AccountAggregate(mockService); + + var shortName = "rg-x"; + aggregate.addPersonalResourceGroup(shortName, "Old Name"); + aggregate.addPersonalResourceGroup(shortName, "New Name"); + + when(mockService.getPersonalResourceGroupByShortName(eq(shortName))).thenReturn(null); + when(mockService.upsertPersonalResourceGroup(eq(shortName), eq("New Name"))) + .thenReturn(mock(PersonalResourceGroupResponse.class)); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(shortName)); + verify(mockService, times(1)).upsertPersonalResourceGroup(eq(shortName), eq("New Name")); + verifyNoMoreInteractions(mockService); + } + + @Test + void doesNothing_when_NoResourceGroupsStaged() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new AccountAggregate(mockService); + + aggregate.createOrUpdate(); + + verifyNoMoreInteractions(mockService); + } + + @Test + void createsMultipleResourceGroups_when_MultipleStaged() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new AccountAggregate(mockService); + + aggregate.addPersonalResourceGroup("rg-a", "A"); + aggregate.addPersonalResourceGroup("rg-b", "B"); + + when(mockService.getPersonalResourceGroupByShortName("rg-a")).thenReturn(null); + when(mockService.getPersonalResourceGroupByShortName("rg-b")).thenReturn(null); + + when(mockService.upsertPersonalResourceGroup("rg-a", "A")).thenReturn(mock(PersonalResourceGroupResponse.class)); + when(mockService.upsertPersonalResourceGroup("rg-b", "B")).thenReturn(mock(PersonalResourceGroupResponse.class)); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getPersonalResourceGroupByShortName("rg-a"); + verify(mockService, times(1)).getPersonalResourceGroupByShortName("rg-b"); + verify(mockService, times(1)).upsertPersonalResourceGroup("rg-a", "A"); + verify(mockService, times(1)).upsertPersonalResourceGroup("rg-b", "B"); + verifyNoMoreInteractions(mockService); + } + + @Test + void propagatesException_when_ServiceGetFails() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new AccountAggregate(mockService); + + var shortName = "rg-error"; + aggregate.addPersonalResourceGroup(shortName, "X"); + + when(mockService.getPersonalResourceGroupByShortName(eq(shortName))) + .thenThrow(new InstantiatorException("boom")); + + assertThatThrownBy(aggregate::createOrUpdate) + .isInstanceOf(InstantiatorException.class) + .hasMessageContaining("boom"); + } } \ No newline at end of file From 86884154df3c6481b3618087a5b2f8dcbf934abb Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 10:19:58 +0200 Subject: [PATCH 09/18] Domain tests --- .../domain/account/AccountAggregateTest.java | 61 +++++---- .../account/OrganizationAggregateTest.java | 123 ++++++++++++++++++ 2 files changed, 152 insertions(+), 32 deletions(-) create mode 100644 fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationAggregateTest.java diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java index 684b5c9f..2d30dbef 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountAggregateTest.java @@ -44,10 +44,10 @@ void createsPersonalResourceGroup_when_NotExists() throws InstantiatorException when(mockService.upsertPersonalResourceGroup(eq(SHORT_NAME), eq(DISPLAY_NAME))) .thenReturn(createdResponse); - // Act + aggregate.createOrUpdate(); - // Assert + verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(SHORT_NAME)); verify(mockService, times(1)).upsertPersonalResourceGroup(eq(SHORT_NAME), eq(DISPLAY_NAME)); verifyNoMoreInteractions(mockService); @@ -55,17 +55,15 @@ void createsPersonalResourceGroup_when_NotExists() throws InstantiatorException @Test void skipsUpsert_when_AlreadyExists() throws InstantiatorException { - // Arrange + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); var aggregate = new AccountAggregate(mockService); - var shortName = "short-name-rg"; - var displayName = "Display Name RG"; - aggregate.addPersonalResourceGroup(shortName, displayName); + aggregate.addPersonalResourceGroup(SHORT_NAME, DISPLAY_NAME); var existingResponse = new PersonalResourceGroupResponse( - ResourceGroupId.fromString(String.format("Personal/%s/%s", UUID.randomUUID(), shortName)), - "Existing Bob", + ResourceGroupId.fromString(String.format("Personal/%s/%s", UUID.randomUUID(), SHORT_NAME)), + "Existing RG", null, EntityStatus.ACTIVE, null, @@ -73,11 +71,11 @@ void skipsUpsert_when_AlreadyExists() throws InstantiatorException { List.of() ); - when(mockService.getPersonalResourceGroupByShortName(eq(shortName))).thenReturn(existingResponse); + when(mockService.getPersonalResourceGroupByShortName(eq(SHORT_NAME))).thenReturn(existingResponse); aggregate.createOrUpdate(); - verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(shortName)); + verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(SHORT_NAME)); verify(mockService, never()).upsertPersonalResourceGroup(anyString(), anyString()); verifyNoMoreInteractions(mockService); } @@ -87,18 +85,18 @@ void updatesDisplayName_when_AddedTwiceWithDifferentDisplayName() throws Instant var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); var aggregate = new AccountAggregate(mockService); - var shortName = "rg-x"; - aggregate.addPersonalResourceGroup(shortName, "Old Name"); - aggregate.addPersonalResourceGroup(shortName, "New Name"); - when(mockService.getPersonalResourceGroupByShortName(eq(shortName))).thenReturn(null); - when(mockService.upsertPersonalResourceGroup(eq(shortName), eq("New Name"))) + aggregate.addPersonalResourceGroup(SHORT_NAME, "Old Name"); + aggregate.addPersonalResourceGroup(SHORT_NAME, "New Name"); + + when(mockService.getPersonalResourceGroupByShortName(eq(SHORT_NAME))).thenReturn(null); + when(mockService.upsertPersonalResourceGroup(eq(SHORT_NAME), eq("New Name"))) .thenReturn(mock(PersonalResourceGroupResponse.class)); aggregate.createOrUpdate(); - verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(shortName)); - verify(mockService, times(1)).upsertPersonalResourceGroup(eq(shortName), eq("New Name")); + verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(SHORT_NAME)); + verify(mockService, times(1)).upsertPersonalResourceGroup(eq(SHORT_NAME), eq("New Name")); verifyNoMoreInteractions(mockService); } @@ -117,21 +115,21 @@ void createsMultipleResourceGroups_when_MultipleStaged() throws InstantiatorExce var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); var aggregate = new AccountAggregate(mockService); - aggregate.addPersonalResourceGroup("rg-a", "A"); - aggregate.addPersonalResourceGroup("rg-b", "B"); + aggregate.addPersonalResourceGroup(SHORT_NAME + "a", DISPLAY_NAME + "A"); + aggregate.addPersonalResourceGroup(SHORT_NAME + "b", DISPLAY_NAME + "B"); - when(mockService.getPersonalResourceGroupByShortName("rg-a")).thenReturn(null); - when(mockService.getPersonalResourceGroupByShortName("rg-b")).thenReturn(null); + when(mockService.getPersonalResourceGroupByShortName(SHORT_NAME + "a")).thenReturn(null); + when(mockService.getPersonalResourceGroupByShortName(SHORT_NAME + "b")).thenReturn(null); - when(mockService.upsertPersonalResourceGroup("rg-a", "A")).thenReturn(mock(PersonalResourceGroupResponse.class)); - when(mockService.upsertPersonalResourceGroup("rg-b", "B")).thenReturn(mock(PersonalResourceGroupResponse.class)); + when(mockService.upsertPersonalResourceGroup(SHORT_NAME + "a", DISPLAY_NAME + "A")).thenReturn(mock(PersonalResourceGroupResponse.class)); + when(mockService.upsertPersonalResourceGroup(SHORT_NAME + "b", DISPLAY_NAME + "B")).thenReturn(mock(PersonalResourceGroupResponse.class)); aggregate.createOrUpdate(); - verify(mockService, times(1)).getPersonalResourceGroupByShortName("rg-a"); - verify(mockService, times(1)).getPersonalResourceGroupByShortName("rg-b"); - verify(mockService, times(1)).upsertPersonalResourceGroup("rg-a", "A"); - verify(mockService, times(1)).upsertPersonalResourceGroup("rg-b", "B"); + verify(mockService, times(1)).getPersonalResourceGroupByShortName(SHORT_NAME + "a"); + verify(mockService, times(1)).getPersonalResourceGroupByShortName(SHORT_NAME + "b"); + verify(mockService, times(1)).upsertPersonalResourceGroup(SHORT_NAME + "a", DISPLAY_NAME + "A"); + verify(mockService, times(1)).upsertPersonalResourceGroup(SHORT_NAME + "b", DISPLAY_NAME + "B"); verifyNoMoreInteractions(mockService); } @@ -140,14 +138,13 @@ void propagatesException_when_ServiceGetFails() throws InstantiatorException { var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); var aggregate = new AccountAggregate(mockService); - var shortName = "rg-error"; - aggregate.addPersonalResourceGroup(shortName, "X"); + aggregate.addPersonalResourceGroup(SHORT_NAME, DISPLAY_NAME); - when(mockService.getPersonalResourceGroupByShortName(eq(shortName))) - .thenThrow(new InstantiatorException("boom")); + when(mockService.getPersonalResourceGroupByShortName(eq(SHORT_NAME))) + .thenThrow(new InstantiatorException("error")); assertThatThrownBy(aggregate::createOrUpdate) .isInstanceOf(InstantiatorException.class) - .hasMessageContaining("boom"); + .hasMessageContaining("error"); } } \ No newline at end of file diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationAggregateTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationAggregateTest.java new file mode 100644 index 00000000..c94d4a99 --- /dev/null +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationAggregateTest.java @@ -0,0 +1,123 @@ +package com.yanchware.fractal.sdk.domain.account; + +import com.yanchware.fractal.sdk.domain.accounts.OrganizationAggregate; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.OrganizationalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +public class OrganizationAggregateTest { + private static final String SHORT_NAME = "test-rg"; + private static final String DISPLAY_NAME = "Test Resource Group"; + + @Test + void createsOrganizationalResourceGroup_when_NotExists() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new OrganizationAggregate(mockService); + + var orgId = UUID.randomUUID(); + aggregate.addOrganizationalResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME); + + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(SHORT_NAME))).thenReturn(null); + when(mockService.upsertOrganizationalResourceGroup(eq(orgId), eq(SHORT_NAME), eq(DISPLAY_NAME))).thenReturn(mock(OrganizationalResourceGroupResponse.class)); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(SHORT_NAME)); + verify(mockService, times(1)).upsertOrganizationalResourceGroup(eq(orgId), eq(SHORT_NAME), eq(DISPLAY_NAME)); + verifyNoMoreInteractions(mockService); + } + + @Test + void skipsUpsert_when_AlreadyExists() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new OrganizationAggregate(mockService); + + var orgId = UUID.randomUUID(); + aggregate.addOrganizationalResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME); + + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(SHORT_NAME))).thenReturn(mock(OrganizationalResourceGroupResponse.class)); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(SHORT_NAME)); + verify(mockService, never()).upsertOrganizationalResourceGroup(eq(orgId), eq(SHORT_NAME), anyString()); + verifyNoMoreInteractions(mockService); + } + + @Test + void updatesDisplayName_when_AddedTwiceWithDifferentDisplayName() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new OrganizationAggregate(mockService); + + var orgId = UUID.randomUUID(); + aggregate.addOrganizationalResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME); + aggregate.addOrganizationalResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME + " v2"); + + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(SHORT_NAME))).thenReturn(null); + when(mockService.upsertOrganizationalResourceGroup(eq(orgId), eq(SHORT_NAME), eq(DISPLAY_NAME + " v2"))).thenReturn(mock(OrganizationalResourceGroupResponse.class)); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(SHORT_NAME)); + verify(mockService, times(1)).upsertOrganizationalResourceGroup(eq(orgId), eq(SHORT_NAME), eq(DISPLAY_NAME + " v2")); + verifyNoMoreInteractions(mockService); + } + + @Test + void doesNothing_when_NoResourceGroupsStaged() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new OrganizationAggregate(mockService); + + aggregate.createOrUpdate(); + + verifyNoMoreInteractions(mockService); + } + + @Test + void createsMultipleResourceGroups_when_MultipleStaged() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new OrganizationAggregate(mockService); + + var orgId = UUID.randomUUID(); + var shortNameA = SHORT_NAME + "-a"; + var shortNameB = SHORT_NAME + "-b"; + var displayA = DISPLAY_NAME + " A"; + var displayB = DISPLAY_NAME + " B"; + + aggregate.addOrganizationalResourceGroup(orgId, shortNameA, displayA); + aggregate.addOrganizationalResourceGroup(orgId, shortNameB, displayB); + + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortNameA))).thenReturn(null); + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortNameB))).thenReturn(null); + + when(mockService.upsertOrganizationalResourceGroup(eq(orgId), eq(shortNameA), eq(displayA))).thenReturn(mock(OrganizationalResourceGroupResponse.class)); + when(mockService.upsertOrganizationalResourceGroup(eq(orgId), eq(shortNameB), eq(displayB))).thenReturn(mock(OrganizationalResourceGroupResponse.class)); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortNameA)); + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortNameB)); + verify(mockService, times(1)).upsertOrganizationalResourceGroup(eq(orgId), eq(shortNameA), eq(displayA)); + verify(mockService, times(1)).upsertOrganizationalResourceGroup(eq(orgId), eq(shortNameB), eq(displayB)); + verifyNoMoreInteractions(mockService); + } + + @Test + void propagatesException_when_ServiceGetFails() throws InstantiatorException { + var mockService = mock(com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService.class); + var aggregate = new OrganizationAggregate(mockService); + + var orgId = UUID.randomUUID(); + aggregate.addOrganizationalResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME); + + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(SHORT_NAME))).thenThrow(new InstantiatorException("error")); + + assertThatThrownBy(aggregate::createOrUpdate).isInstanceOf(InstantiatorException.class).hasMessageContaining("error"); + } +} From f859cf0a60ad248214987439f0ce460b7e1851ab Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 11:33:32 +0200 Subject: [PATCH 10/18] Tests for factory and service --- .../sdk/domain/accounts/AccountsFactory.java | 4 + .../accounts/service/RestAccountsService.java | 4 +- .../domain/account/AccountFactoryTest.java | 27 +++ .../account/OrganizationFactoryTest.java | 30 ++++ .../account/RestAccountsServiceTest.java | 170 ++++++++++++++++++ 5 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java create mode 100644 fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java create mode 100644 fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java index 03733566..33386cd3 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/AccountsFactory.java @@ -40,5 +40,9 @@ public AccountBuilder withResourceGroup(String shortName, String displayName) { aggregate.addPersonalResourceGroup(shortName, displayName); return builder; } + + public AccountAggregate build() { + return aggregate; + } } } diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java index e39f9b40..6cf8cfeb 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java @@ -47,7 +47,7 @@ public OrganizationalResourceGroupResponse getOrganizationalResourceGroupByShort HttpUtils.buildGetRequest( getOrganizationalResourceGroupsUri(organizationId, shortName), sdkConfiguration), - new int[]{200}, + new int[]{200, 404}, OrganizationalResourceGroupResponse.class); } @@ -77,7 +77,7 @@ public PersonalResourceGroupResponse getPersonalResourceGroupByShortName(String HttpUtils.buildGetRequest( getPersonalResourceGroupsUri(shortName), sdkConfiguration), - new int[]{200}, + new int[]{200, 404}, PersonalResourceGroupResponse.class); } diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java new file mode 100644 index 00000000..e0e3122f --- /dev/null +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java @@ -0,0 +1,27 @@ +package com.yanchware.fractal.sdk.domain.account; + +import com.yanchware.fractal.sdk.domain.accounts.AccountsFactory; +import com.yanchware.fractal.sdk.utils.LocalSdkConfiguration; +import io.github.resilience4j.retry.RetryRegistry; +import org.junit.jupiter.api.Test; + +import java.net.http.HttpClient; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class AccountFactoryTest { + @Test + void builder_buildsAggregate_and_stagesPersonalResourceGroup() throws Exception { + var httpClient = HttpClient.newHttpClient(); + var sdkConfig = new LocalSdkConfiguration("http://localhost:8080"); // test config + var retry = RetryRegistry.ofDefaults(); + + var factory = new AccountsFactory(httpClient, sdkConfig, retry); + var aggregate = factory.builder() + .withResourceGroup("rg-1", "RG One") + .build(); + + assertThat(aggregate).isNotNull(); + + } +} diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java new file mode 100644 index 00000000..fff70f38 --- /dev/null +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java @@ -0,0 +1,30 @@ +package com.yanchware.fractal.sdk.domain.account; + +import com.yanchware.fractal.sdk.domain.accounts.OrganizationFactory; +import com.yanchware.fractal.sdk.utils.LocalSdkConfiguration; +import io.github.resilience4j.retry.RetryRegistry; +import org.junit.jupiter.api.Test; + +import java.net.http.HttpClient; +import java.util.UUID; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +// Java +class OrganizationFactoryTest { + @Test + void builder_buildsAggregate_and_stagesOrganizationalResourceGroup() throws Exception { + var httpClient = HttpClient.newHttpClient(); + var sdkConfig = new LocalSdkConfiguration("http://localhost:8080"); + var retry = RetryRegistry.ofDefaults(); + + var orgId = UUID.randomUUID(); + + var factory = new OrganizationFactory(httpClient, sdkConfig, retry); + var aggregate = factory.builder() + .withResourceGroup(orgId, "rg-1", "RG One") + .build(); + + assertThat(aggregate).isNotNull(); + } +} diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java new file mode 100644 index 00000000..dcaec3dc --- /dev/null +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java @@ -0,0 +1,170 @@ +package com.yanchware.fractal.sdk.domain.account; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.yanchware.fractal.sdk.domain.accounts.EntityStatus; +import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; +import com.yanchware.fractal.sdk.domain.values.ResourceGroupType; +import com.yanchware.fractal.sdk.utils.LocalSdkConfiguration; +import io.github.resilience4j.retry.RetryRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.http.HttpClient; +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.assertj.core.api.Assertions.assertThat; + +@WireMockTest +class RestAccountsServiceTest { + private RestAccountsService accountsService; + private static final String SHORT_NAME = "rg-personal"; + private static final String DISPLAY_NAME = "Personal Resource Group"; + + @BeforeEach + void setUp(WireMockRuntimeInfo wm) { + var httpClient = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_2) + .build(); + + var sdkConfiguration = new LocalSdkConfiguration(wm.getHttpBaseUrl()); + accountsService = new RestAccountsService(httpClient, sdkConfiguration, RetryRegistry.ofDefaults()); + } + + @Test + void upsertPersonalResourceGroup_postsToCorrectUrl_andReturnsResponse() throws Exception { + // Given + var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var accountId = UUID.randomUUID().toString(); + stubFor(post(urlPattern) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.PERSONAL, accountId, SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + // When + var resp = accountsService.upsertPersonalResourceGroup(SHORT_NAME, DISPLAY_NAME); + + // Then + verify(1, postRequestedFor(urlPattern)); + assertThat(resp).isNotNull(); + assertThat(resp.DisplayName()).isEqualTo(DISPLAY_NAME); + } + + @Test + void getPersonalResourceGroupByShortName_throwsInstantiatorException_on500() { + // Given + var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + + stubFor(get(urlPattern) + .willReturn(aResponse() + .withStatus(500) + .withHeader("Content-Type", "application/json") + .withBody("{\"message\":\"Internal Server Error\"}"))); + + // When / Then + org.assertj.core.api.Assertions.assertThatThrownBy(() -> + accountsService.getPersonalResourceGroupByShortName(SHORT_NAME) + ).isInstanceOf(com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException.class); + verify(3, getRequestedFor(urlPattern)); + } + + @Test + void getPersonalResourceGroupByShortName_returnsNull_on404() throws Exception { + // Given + var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + + stubFor(get(urlPattern).willReturn(aResponse().withStatus(404))); + + // When + var resp = accountsService.getPersonalResourceGroupByShortName(SHORT_NAME); + + // Then + verify(1, getRequestedFor(urlPattern)); + assertThat(resp).isNull(); + } + + @Test + void upsertOrganizationalResourceGroup_postsToCorrectUrl_andReturnsResponse() throws Exception { + // Given + var organizationId = UUID.randomUUID(); + var urlPattern = urlPathMatching("/accounts/organizations/" + organizationId + "/resourcegroups/" + SHORT_NAME); + + stubFor(post(urlPattern) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.ORGANIZATIONAL, organizationId, SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + + // When + var resp = accountsService.upsertOrganizationalResourceGroup(organizationId, SHORT_NAME, DISPLAY_NAME); + + // Then + verify(1, postRequestedFor(urlPattern)); + assertThat(resp).isNotNull(); + assertThat(resp.DisplayName()).isEqualTo(DISPLAY_NAME); + } + + @Test + void getOrganizationalResourceGroupByShortName_returnsResponse_on200() throws Exception { + // Given + var organizationId = java.util.UUID.randomUUID(); + var urlPattern = urlPathMatching("/accounts/organizations/" + organizationId + "/resourcegroups/" + SHORT_NAME); + + stubFor(get(urlPattern) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"Organizational/%s/%s","DisplayName":"%s","Status":"Active"} + """.formatted(organizationId, SHORT_NAME, DISPLAY_NAME)))); + + // When + var resp = accountsService.getOrganizationalResourceGroupByShortName(organizationId, SHORT_NAME); + + // Then + verify(1, getRequestedFor(urlPattern)); + assertThat(resp).isNotNull(); + assertThat(resp.DisplayName()).isEqualTo(DISPLAY_NAME); + } + + @Test + void getOrganizationalResourceGroupByShortName_returnsNull_on404() throws Exception { + // Given + var organizationId = java.util.UUID.randomUUID(); + var urlPattern = urlPathMatching("/accounts/organizations/" + organizationId + "/resourcegroups/" + SHORT_NAME); + + stubFor(get(urlPattern).willReturn(aResponse().withStatus(404))); + + // When + var resp = accountsService.getOrganizationalResourceGroupByShortName(organizationId, SHORT_NAME); + + // Then + verify(1, getRequestedFor(urlPattern)); + assertThat(resp).isNull(); + } + + @Test + void getOrganizationalResourceGroupByShortName_throwsInstantiatorException_on500() { + // Given + var organizationId = java.util.UUID.randomUUID(); + var urlPattern = urlPathMatching("/accounts/organizations/" + organizationId + "/resourcegroups/" + SHORT_NAME); + + stubFor(get(urlPattern) + .willReturn(aResponse() + .withStatus(500) + .withHeader("Content-Type", "application/json") + .withBody("{\"message\":\"Internal Server Error\"}"))); + + // When / Then + org.assertj.core.api.Assertions.assertThatThrownBy(() -> + accountsService.getOrganizationalResourceGroupByShortName(organizationId, SHORT_NAME) + ).isInstanceOf(com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException.class); + + verify(3, getRequestedFor(urlPattern)); + } +} From 01d6d674f16f4f2006b456ff67fc98c6ec26b126 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 11:56:59 +0200 Subject: [PATCH 11/18] Added tests for rest account service --- .../account/RestAccountsServiceTest.java | 124 ++++++++++++++++-- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java index dcaec3dc..21a9c70c 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java @@ -53,6 +53,69 @@ void upsertPersonalResourceGroup_postsToCorrectUrl_andReturnsResponse() throws E assertThat(resp.DisplayName()).isEqualTo(DISPLAY_NAME); } + @Test + void upsertPersonalResourceGroup_sendsDisplayNameInBody() throws Exception { + // Given + var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var accountId = UUID.randomUUID().toString(); + stubFor(post(urlPattern) + .withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME))) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.PERSONAL, accountId, SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + + // When + var resp = accountsService.upsertPersonalResourceGroup(SHORT_NAME, DISPLAY_NAME); + + // Then + verify(postRequestedFor(urlPattern).withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME)))); + assertThat(resp).isNotNull(); + assertThat(resp.DisplayName()).isEqualTo(DISPLAY_NAME); + } + + @Test + void upsertPersonalResourceGroup_throwsInstantiatorException_on500() { + // Given + var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + + stubFor(post(urlPattern) + .willReturn(aResponse() + .withStatus(500) + .withHeader("Content-Type", "application/json") + .withBody("{\"message\":\"Internal Server Error\"}"))); + + // When / Then + org.assertj.core.api.Assertions.assertThatThrownBy(() -> + accountsService.upsertPersonalResourceGroup(SHORT_NAME, DISPLAY_NAME) + ).isInstanceOf(com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException.class); + } + + @Test + void getPersonalResourceGroupByShortName_returnsResponse_on200() throws Exception { + // Given + var accountId = java.util.UUID.randomUUID(); + var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + + stubFor(get(urlPattern) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.PERSONAL, accountId, SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + + // When + var resp = accountsService.getPersonalResourceGroupByShortName(SHORT_NAME); + + // Then + verify(1, getRequestedFor(urlPattern)); + assertThat(resp).isNotNull(); + assertThat(resp.DisplayName()).isEqualTo(DISPLAY_NAME); + } + @Test void getPersonalResourceGroupByShortName_throwsInstantiatorException_on500() { // Given @@ -110,42 +173,69 @@ void upsertOrganizationalResourceGroup_postsToCorrectUrl_andReturnsResponse() th } @Test - void getOrganizationalResourceGroupByShortName_returnsResponse_on200() throws Exception { + void upsertOrganizationalResourceGroup_sendsDisplayNameInBody() throws Exception { // Given var organizationId = java.util.UUID.randomUUID(); var urlPattern = urlPathMatching("/accounts/organizations/" + organizationId + "/resourcegroups/" + SHORT_NAME); - stubFor(get(urlPattern) + stubFor(post(urlPattern) + .withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME))) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody(""" - {"Id":"Organizational/%s/%s","DisplayName":"%s","Status":"Active"} - """.formatted(organizationId, SHORT_NAME, DISPLAY_NAME)))); + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.ORGANIZATIONAL, organizationId, SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + // When - var resp = accountsService.getOrganizationalResourceGroupByShortName(organizationId, SHORT_NAME); + var resp = accountsService.upsertOrganizationalResourceGroup(organizationId, SHORT_NAME, DISPLAY_NAME); // Then - verify(1, getRequestedFor(urlPattern)); + verify(postRequestedFor(urlPattern).withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME)))); assertThat(resp).isNotNull(); assertThat(resp.DisplayName()).isEqualTo(DISPLAY_NAME); } @Test - void getOrganizationalResourceGroupByShortName_returnsNull_on404() throws Exception { + void upsertOrganizationalResourceGroup_throwsInstantiatorException_on500() { // Given var organizationId = java.util.UUID.randomUUID(); var urlPattern = urlPathMatching("/accounts/organizations/" + organizationId + "/resourcegroups/" + SHORT_NAME); - stubFor(get(urlPattern).willReturn(aResponse().withStatus(404))); + stubFor(post(urlPattern) + .willReturn(aResponse() + .withStatus(500) + .withHeader("Content-Type", "application/json") + .withBody("{\"message\":\"Internal Server Error\"}"))); + + // When / Then + org.assertj.core.api.Assertions.assertThatThrownBy(() -> + accountsService.upsertOrganizationalResourceGroup(organizationId, SHORT_NAME, DISPLAY_NAME) + ).isInstanceOf(com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException.class); + } + + @Test + void getOrganizationalResourceGroupByShortName_returnsResponse_on200() throws Exception { + // Given + var organizationId = java.util.UUID.randomUUID(); + var urlPattern = urlPathMatching("/accounts/organizations/" + organizationId + "/resourcegroups/" + SHORT_NAME); + + stubFor(get(urlPattern) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.ORGANIZATIONAL, organizationId, SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); // When var resp = accountsService.getOrganizationalResourceGroupByShortName(organizationId, SHORT_NAME); // Then verify(1, getRequestedFor(urlPattern)); - assertThat(resp).isNull(); + assertThat(resp).isNotNull(); + assertThat(resp.DisplayName()).isEqualTo(DISPLAY_NAME); } @Test @@ -167,4 +257,20 @@ void getOrganizationalResourceGroupByShortName_throwsInstantiatorException_on500 verify(3, getRequestedFor(urlPattern)); } + + @Test + void getOrganizationalResourceGroupByShortName_returnsNull_on404() throws Exception { + // Given + var organizationId = java.util.UUID.randomUUID(); + var urlPattern = urlPathMatching("/accounts/organizations/" + organizationId + "/resourcegroups/" + SHORT_NAME); + + stubFor(get(urlPattern).willReturn(aResponse().withStatus(404))); + + // When + var resp = accountsService.getOrganizationalResourceGroupByShortName(organizationId, SHORT_NAME); + + // Then + verify(1, getRequestedFor(urlPattern)); + assertThat(resp).isNull(); + } } From 038e6a6aff35d695952d93e93625e14744173eef Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 12:00:55 +0200 Subject: [PATCH 12/18] Added tests for account factory --- .../fractal/sdk/domain/account/AccountFactoryTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java index e0e3122f..52615700 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java @@ -10,6 +10,9 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class AccountFactoryTest { + private static final String SHORT_NAME= "rg-1"; + private static final String DISPLAY_NAME = "RG One"; + @Test void builder_buildsAggregate_and_stagesPersonalResourceGroup() throws Exception { var httpClient = HttpClient.newHttpClient(); @@ -18,10 +21,12 @@ void builder_buildsAggregate_and_stagesPersonalResourceGroup() throws Exception var factory = new AccountsFactory(httpClient, sdkConfig, retry); var aggregate = factory.builder() - .withResourceGroup("rg-1", "RG One") + .withResourceGroup(SHORT_NAME, DISPLAY_NAME) .build(); assertThat(aggregate).isNotNull(); } + + } From b0a2f134cac4398debb460bf4f42a9d0b606e98a Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 12:14:19 +0200 Subject: [PATCH 13/18] Added tests for organization factory, removed hardcoded values in AccountFactoryTest --- .../domain/account/AccountFactoryTest.java | 158 ++++++++++++++- .../account/OrganizationFactoryTest.java | 180 ++++++++++++++++-- 2 files changed, 318 insertions(+), 20 deletions(-) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java index 52615700..5030ec30 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java @@ -1,22 +1,34 @@ package com.yanchware.fractal.sdk.domain.account; +import com.yanchware.fractal.sdk.configuration.SdkConfiguration; +import com.yanchware.fractal.sdk.domain.accounts.AccountAggregate; import com.yanchware.fractal.sdk.domain.accounts.AccountsFactory; +import com.yanchware.fractal.sdk.domain.accounts.EntityStatus; +import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.PersonalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; +import com.yanchware.fractal.sdk.domain.values.ResourceGroupId; +import com.yanchware.fractal.sdk.domain.values.ResourceGroupType; import com.yanchware.fractal.sdk.utils.LocalSdkConfiguration; import io.github.resilience4j.retry.RetryRegistry; import org.junit.jupiter.api.Test; - import java.net.http.HttpClient; - +import java.util.List; +import java.util.UUID; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; public class AccountFactoryTest { - private static final String SHORT_NAME= "rg-1"; - private static final String DISPLAY_NAME = "RG One"; + + private static final String BASE_URL = "http://localhost:8080"; + private static final String SHORT_NAME = "rg-1"; + private static final String DISPLAY_NAME = "RG One"; @Test void builder_buildsAggregate_and_stagesPersonalResourceGroup() throws Exception { var httpClient = HttpClient.newHttpClient(); - var sdkConfig = new LocalSdkConfiguration("http://localhost:8080"); // test config + var sdkConfig = new LocalSdkConfiguration(BASE_URL); var retry = RetryRegistry.ofDefaults(); var factory = new AccountsFactory(httpClient, sdkConfig, retry); @@ -25,8 +37,144 @@ void builder_buildsAggregate_and_stagesPersonalResourceGroup() throws Exception .build(); assertThat(aggregate).isNotNull(); + } + + @Test + void builder_isFluent_and_returnsSameInstance() { + var httpClient = HttpClient.newHttpClient(); + var sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var builder = new AccountsFactory(httpClient, sdkConfig, retry).builder(); + var returned = builder.withResourceGroup(SHORT_NAME, DISPLAY_NAME); + + assertThat(returned).isSameAs(builder); + } + + @Test + void build_returnsSameAggregate_onRepeatedBuildCalls() { + var httpClient = HttpClient.newHttpClient(); + var sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var builder = new AccountsFactory(httpClient, sdkConfig, retry).builder().withResourceGroup(SHORT_NAME, DISPLAY_NAME); + var aggregate1 = builder.build(); + var aggregate2 = builder.build(); + + assertThat(aggregate1).isSameAs(aggregate2); + } + + @Test + void withResourceGroup_stages_and_createOrUpdate_callsService_when_NotExists() throws InstantiatorException { + var httpClient = HttpClient.newHttpClient(); + SdkConfiguration sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var mockService = mock(RestAccountsService.class); + + when(mockService.getPersonalResourceGroupByShortName(eq(SHORT_NAME))).thenReturn(null); + + var createdResponse = new PersonalResourceGroupResponse( + ResourceGroupId.fromString(String.format("%s/%s/%s", ResourceGroupType.PERSONAL, UUID.randomUUID(), SHORT_NAME)), + DISPLAY_NAME, + null, + EntityStatus.ACTIVE, + null, + List.of(), + List.of() + ); + when(mockService.upsertPersonalResourceGroup(eq(SHORT_NAME), eq(DISPLAY_NAME))).thenReturn(createdResponse); + + var builder = new AccountsFactory.AccountBuilder(httpClient, sdkConfig, retry) { + @Override + protected AccountAggregate create(HttpClient client, SdkConfiguration cfg, RetryRegistry reg) { + return new AccountAggregate(mockService); + } + }; + + var aggregate = builder + .withResourceGroup(SHORT_NAME, DISPLAY_NAME) + .build(); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getPersonalResourceGroupByShortName(eq(SHORT_NAME)); + verify(mockService, times(1)).upsertPersonalResourceGroup(eq(SHORT_NAME), eq(DISPLAY_NAME)); + verifyNoMoreInteractions(mockService); + } + + @Test + void withResourceGroup_chainMultiple_stagesAll_and_callsService() throws InstantiatorException { + var httpClient = HttpClient.newHttpClient(); + SdkConfiguration sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var mockService = mock(RestAccountsService.class); + + var shortNameA = SHORT_NAME + "-a"; + var displayA = DISPLAY_NAME + " A"; + var shortNameB = SHORT_NAME + "-b"; + var displayB = DISPLAY_NAME + " B"; + + + when(mockService.getPersonalResourceGroupByShortName(shortNameA)).thenReturn(null); + when(mockService.getPersonalResourceGroupByShortName(shortNameB)).thenReturn(null); + + when(mockService.upsertPersonalResourceGroup(shortNameA, displayA)).thenReturn(mock(PersonalResourceGroupResponse.class)); + when(mockService.upsertPersonalResourceGroup(shortNameB, displayB)).thenReturn(mock(PersonalResourceGroupResponse.class)); + + var builder = new AccountsFactory.AccountBuilder(httpClient, sdkConfig, retry) { + @Override + protected AccountAggregate create(HttpClient client, SdkConfiguration cfg, RetryRegistry reg) { + return new AccountAggregate(mockService); + } + }; + var aggregate = builder + .withResourceGroup(shortNameA, displayA) + .withResourceGroup(shortNameB, displayB) + .build(); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getPersonalResourceGroupByShortName(shortNameA); + verify(mockService, times(1)).getPersonalResourceGroupByShortName(shortNameB); + verify(mockService, times(1)).upsertPersonalResourceGroup(shortNameA, displayA); + verify(mockService, times(1)).upsertPersonalResourceGroup(shortNameB, displayB); + verifyNoMoreInteractions(mockService); } + @Test + void withResourceGroup_duplicateShortName_updatesStagedDisplayName() throws InstantiatorException { + var httpClient = HttpClient.newHttpClient(); + SdkConfiguration sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var mockService = mock(RestAccountsService.class); + var shortName = "rg-dup"; + var oldName = "Old"; + var newName = "New"; + + when(mockService.getPersonalResourceGroupByShortName(shortName)).thenReturn(null); + when(mockService.upsertPersonalResourceGroup(shortName, newName)).thenReturn(mock(PersonalResourceGroupResponse.class)); + + var builder = new AccountsFactory.AccountBuilder(httpClient, sdkConfig, retry) { + @Override + protected AccountAggregate create(HttpClient client, SdkConfiguration cfg, RetryRegistry reg) { + return new AccountAggregate(mockService); + } + }; + + var aggregate = builder + .withResourceGroup(shortName, oldName) + .withResourceGroup(shortName, newName) + .build(); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getPersonalResourceGroupByShortName(shortName); + verify(mockService, times(1)).upsertPersonalResourceGroup(shortName, newName); + verifyNoMoreInteractions(mockService); + } } diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java index fff70f38..752593da 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java @@ -1,30 +1,180 @@ package com.yanchware.fractal.sdk.domain.account; import com.yanchware.fractal.sdk.domain.accounts.OrganizationFactory; +import com.yanchware.fractal.sdk.configuration.SdkConfiguration; +import com.yanchware.fractal.sdk.domain.accounts.OrganizationAggregate; +import com.yanchware.fractal.sdk.domain.accounts.service.RestAccountsService; +import com.yanchware.fractal.sdk.domain.accounts.service.dtos.OrganizationalResourceGroupResponse; +import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; import com.yanchware.fractal.sdk.utils.LocalSdkConfiguration; import io.github.resilience4j.retry.RetryRegistry; import org.junit.jupiter.api.Test; - import java.net.http.HttpClient; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -// Java class OrganizationFactoryTest { - @Test - void builder_buildsAggregate_and_stagesOrganizationalResourceGroup() throws Exception { - var httpClient = HttpClient.newHttpClient(); - var sdkConfig = new LocalSdkConfiguration("http://localhost:8080"); - var retry = RetryRegistry.ofDefaults(); + private static final String BASE_URL = "http://localhost:8080"; + private static final String SHORT_NAME = "rg-1"; + private static final String DISPLAY_NAME = "RG One"; + + @Test + void builder_buildsAggregate_and_stagesOrganizationalResourceGroup() throws Exception { + var httpClient = HttpClient.newHttpClient(); + var sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var orgId = UUID.randomUUID(); + + var factory = new OrganizationFactory(httpClient, sdkConfig, retry); + var aggregate = factory.builder() + .withResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME) + .build(); + + assertThat(aggregate).isNotNull(); + } + + @Test + void builder_isFluent_and_returnsSameInstance() { + var httpClient = HttpClient.newHttpClient(); + var sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var orgId = UUID.randomUUID(); + + var builder = new OrganizationFactory(httpClient, sdkConfig, retry).builder(); + var returned = builder.withResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME); + + assertThat(returned).isSameAs(builder); + } + + @Test + void build_returnsSameAggregate_onRepeatedBuildCalls() { + var httpClient = HttpClient.newHttpClient(); + var sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var orgId = UUID.randomUUID(); + var builder = new OrganizationFactory(httpClient, sdkConfig, retry).builder().withResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME); + + var aggregate1 = builder.build(); + var aggregate2 = builder.build(); + + assertThat(aggregate1).isSameAs(aggregate2); + } + + @Test + void withResourceGroup_stages_and_createOrUpdate_callsService_when_NotExists() throws InstantiatorException { + var httpClient = HttpClient.newHttpClient(); + SdkConfiguration sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var mockService = mock(RestAccountsService.class); + + var orgId = UUID.randomUUID(); + var shortName = SHORT_NAME; + var displayName = DISPLAY_NAME; + + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortName))).thenReturn(null); + when(mockService.upsertOrganizationalResourceGroup(eq(orgId), eq(shortName), eq(displayName))) + .thenReturn(mock(OrganizationalResourceGroupResponse.class)); + + var builder = new OrganizationFactory.OrganizationBuilder(httpClient, sdkConfig, retry) { + @Override + protected OrganizationAggregate create(HttpClient client, SdkConfiguration cfg, RetryRegistry reg) { + return new OrganizationAggregate(mockService); + } + }; + + var aggregate = builder + .withResourceGroup(orgId, shortName, displayName) + .build(); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortName)); + verify(mockService, times(1)).upsertOrganizationalResourceGroup(eq(orgId), eq(shortName), eq(displayName)); + verifyNoMoreInteractions(mockService); + } + + @Test + void withResourceGroup_chainMultiple_stagesAll_and_callsService() throws InstantiatorException { + var httpClient = HttpClient.newHttpClient(); + SdkConfiguration sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var mockService = mock(RestAccountsService.class); + + var orgId = UUID.randomUUID(); + var shortNameA = SHORT_NAME + "-a"; + var displayA = DISPLAY_NAME + " A"; + var shortNameB = SHORT_NAME + "-b"; + var displayB = DISPLAY_NAME + " B"; + + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortNameA))).thenReturn(null); + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortNameB))).thenReturn(null); + + when(mockService.upsertOrganizationalResourceGroup(eq(orgId), eq(shortNameA), eq(displayA))) + .thenReturn(mock(OrganizationalResourceGroupResponse.class)); + when(mockService.upsertOrganizationalResourceGroup(eq(orgId), eq(shortNameB), eq(displayB))) + .thenReturn(mock(OrganizationalResourceGroupResponse.class)); + + var builder = new OrganizationFactory.OrganizationBuilder(httpClient, sdkConfig, retry) { + @Override + protected OrganizationAggregate create(HttpClient client, SdkConfiguration cfg, RetryRegistry reg) { + return new OrganizationAggregate(mockService); + } + }; + + var aggregate = builder + .withResourceGroup(orgId, shortNameA, displayA) + .withResourceGroup(orgId, shortNameB, displayB) + .build(); + + aggregate.createOrUpdate(); + + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortNameA)); + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortNameB)); + verify(mockService, times(1)).upsertOrganizationalResourceGroup(eq(orgId), eq(shortNameA), eq(displayA)); + verify(mockService, times(1)).upsertOrganizationalResourceGroup(eq(orgId), eq(shortNameB), eq(displayB)); + verifyNoMoreInteractions(mockService); + } + + @Test + void withResourceGroup_duplicateIdentity_updatesStagedDisplayName() throws InstantiatorException { + var httpClient = HttpClient.newHttpClient(); + SdkConfiguration sdkConfig = new LocalSdkConfiguration(BASE_URL); + var retry = RetryRegistry.ofDefaults(); + + var mockService = mock(RestAccountsService.class); + + var orgId = UUID.randomUUID(); + var shortName = SHORT_NAME; + var newDisplayName = DISPLAY_NAME + " v2"; + + when(mockService.getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortName))).thenReturn(null); + when(mockService.upsertOrganizationalResourceGroup(eq(orgId), eq(shortName), eq(newDisplayName))) + .thenReturn(mock(OrganizationalResourceGroupResponse.class)); + + var builder = new OrganizationFactory.OrganizationBuilder(httpClient, sdkConfig, retry) { + @Override + protected OrganizationAggregate create(HttpClient client, SdkConfiguration cfg, RetryRegistry reg) { + return new OrganizationAggregate(mockService); + } + }; - var orgId = UUID.randomUUID(); + var aggregate = builder + .withResourceGroup(orgId, shortName, DISPLAY_NAME) + .withResourceGroup(orgId, shortName, newDisplayName) // overwrite staged display + .build(); - var factory = new OrganizationFactory(httpClient, sdkConfig, retry); - var aggregate = factory.builder() - .withResourceGroup(orgId, "rg-1", "RG One") - .build(); + aggregate.createOrUpdate(); - assertThat(aggregate).isNotNull(); - } + verify(mockService, times(1)).getOrganizationalResourceGroupByShortName(eq(orgId), eq(shortName)); + verify(mockService, times(1)).upsertOrganizationalResourceGroup(eq(orgId), eq(shortName), eq(newDisplayName)); + verifyNoMoreInteractions(mockService); + } } From fadec772a12687437cb871166890ddb95da5c091 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 12:30:56 +0200 Subject: [PATCH 14/18] Added tests for organization factory, removed hardcoded values in AccountFactoryTest --- .../fractal/sdk/domain/account/AccountFactoryTest.java | 4 +++- .../fractal/sdk/domain/account/OrganizationFactoryTest.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java index 5030ec30..18eb1261 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/AccountFactoryTest.java @@ -12,9 +12,11 @@ import com.yanchware.fractal.sdk.utils.LocalSdkConfiguration; import io.github.resilience4j.retry.RetryRegistry; import org.junit.jupiter.api.Test; + import java.net.http.HttpClient; import java.util.List; import java.util.UUID; + import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -26,7 +28,7 @@ public class AccountFactoryTest { private static final String DISPLAY_NAME = "RG One"; @Test - void builder_buildsAggregate_and_stagesPersonalResourceGroup() throws Exception { + void builder_buildsAggregate_and_stagesPersonalResourceGroup() { var httpClient = HttpClient.newHttpClient(); var sdkConfig = new LocalSdkConfiguration(BASE_URL); var retry = RetryRegistry.ofDefaults(); diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java index 752593da..1fb65aae 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/OrganizationFactoryTest.java @@ -9,8 +9,10 @@ import com.yanchware.fractal.sdk.utils.LocalSdkConfiguration; import io.github.resilience4j.retry.RetryRegistry; import org.junit.jupiter.api.Test; + import java.net.http.HttpClient; import java.util.UUID; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -22,7 +24,7 @@ class OrganizationFactoryTest { private static final String DISPLAY_NAME = "RG One"; @Test - void builder_buildsAggregate_and_stagesOrganizationalResourceGroup() throws Exception { + void builder_buildsAggregate_and_stagesOrganizationalResourceGroup() { var httpClient = HttpClient.newHttpClient(); var sdkConfig = new LocalSdkConfiguration(BASE_URL); var retry = RetryRegistry.ofDefaults(); From 09143ea6df8166edb76094be3281fa734142ce64 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 17 Sep 2025 12:53:50 +0200 Subject: [PATCH 15/18] Added tests for Automaton --- .../yanchware/fractal/sdk/AutomatonTest.java | 203 +++++++++++++++--- 1 file changed, 173 insertions(+), 30 deletions(-) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/AutomatonTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/AutomatonTest.java index 84a85bf0..9c26cd9d 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/AutomatonTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/AutomatonTest.java @@ -5,54 +5,197 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.yanchware.fractal.sdk.domain.accounts.EntityStatus; import com.yanchware.fractal.sdk.domain.environment.EnvironmentIdValue; import com.yanchware.fractal.sdk.domain.environment.service.dtos.EnvironmentResponse; import com.yanchware.fractal.sdk.domain.exceptions.InstantiatorException; import com.yanchware.fractal.sdk.domain.livesystem.LiveSystemIdValue; +import com.yanchware.fractal.sdk.domain.values.ResourceGroupType; import com.yanchware.fractal.sdk.utils.LocalSdkConfiguration; import org.junit.jupiter.api.Test; import java.net.URISyntaxException; import java.net.http.HttpClient; +import java.util.UUID; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @WireMockTest public class AutomatonTest extends TestWithFixture { + private static final String SHORT_NAME = "rg-sdk"; + private static final String DISPLAY_NAME = "SDK RG"; + + @Test + public void success_when_deleteLiveSystem(WireMockRuntimeInfo wmRuntimeInfo) throws URISyntaxException, InstantiatorException, JsonProcessingException { + var httpClient = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_2) + .build(); + var sdkConfiguration = new LocalSdkConfiguration(wmRuntimeInfo.getHttpBaseUrl()); + Automaton.initializeAutomaton(httpClient, sdkConfiguration); + var sut = Automaton.getInstance(); - @Test - public void success_when_deleteLiveSystem(WireMockRuntimeInfo wmRuntimeInfo) throws URISyntaxException, InstantiatorException, JsonProcessingException { - var httpClient = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_2) - .build(); - var sdkConfiguration = new LocalSdkConfiguration(wmRuntimeInfo.getHttpBaseUrl()); - Automaton.initializeAutomaton(httpClient, sdkConfiguration); - var sut = Automaton.getInstance(); - - var liveSystemIds = aListOf(LiveSystemIdValue.class); - var environmentResponse = a(EnvironmentResponse.class); - var environmentId = environmentResponse.id(); - - stubFor(get(urlPathMatching(String.format("/environments/%s/%s/%s", environmentId.type(), environmentId.ownerId(), environmentId.shortName()))) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBody(new ObjectMapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) - .writeValueAsString(environmentResponse)))); - - stubFor(delete(urlPathMatching("/livesystems/.*")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json"))); - - sut.delete(EnvironmentIdValue.fromDto(environmentResponse.id()), liveSystemIds); - - for(var liveSystemId : liveSystemIds) { - verify(deleteRequestedFor(urlPathEqualTo(String.format("/livesystems/%s", liveSystemId)))); + var liveSystemIds = aListOf(LiveSystemIdValue.class); + var environmentResponse = a(EnvironmentResponse.class); + var environmentId = environmentResponse.id(); + + stubFor(get(urlPathMatching(String.format("/environments/%s/%s/%s", environmentId.type(), environmentId.ownerId(), environmentId.shortName()))) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(new ObjectMapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .writeValueAsString(environmentResponse)))); + + stubFor(delete(urlPathMatching("/livesystems/.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json"))); + + sut.delete(EnvironmentIdValue.fromDto(environmentResponse.id()), liveSystemIds); + + for (var liveSystemId : liveSystemIds) { + verify(deleteRequestedFor(urlPathEqualTo(String.format("/livesystems/%s", liveSystemId)))); + } + } + + @Test + public void instantiateAccount_createsPersonalResourceGroup_whenNotExists(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + var httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); + var sdkConfiguration = new LocalSdkConfiguration(wmRuntimeInfo.getHttpBaseUrl()); + Automaton.initializeAutomaton(httpClient, sdkConfiguration); + var sut = Automaton.getInstance(); + + var getUrl = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + stubFor(get(getUrl).willReturn(aResponse().withStatus(404))); + + stubFor(post(getUrl) + .withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME))) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.PERSONAL, UUID.randomUUID(), SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + + var accountAggregate = sut.getAccountBuilder() + .withResourceGroup(SHORT_NAME, DISPLAY_NAME) + .build(); + + sut.instantiate(accountAggregate); + + verify(1, getRequestedFor(getUrl)); + verify(1, postRequestedFor(getUrl).withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME)))); + } + + @Test + public void instantiateOrganization_createsOrganizationalResourceGroup_whenNotExists(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + var httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); + var sdkConfiguration = new LocalSdkConfiguration(wmRuntimeInfo.getHttpBaseUrl()); + Automaton.initializeAutomaton(httpClient, sdkConfiguration); + var sut = Automaton.getInstance(); + + var orgId = java.util.UUID.randomUUID(); + + var path = String.format("/accounts/organizations/%s/resourcegroups/%s", orgId, SHORT_NAME); + var getUrl = urlPathMatching(path); + stubFor(get(getUrl).willReturn(aResponse().withStatus(404))); + + stubFor(post(getUrl) + .withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME))) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.ORGANIZATIONAL, UUID.randomUUID(), SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + + var orgAggregate = sut.getOrganizationBuilder() + .withResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME) + .build(); + + sut.instantiate(orgAggregate); + + verify(1, getRequestedFor(getUrl)); + verify(1, postRequestedFor(getUrl).withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME)))); } - } + @Test + public void instantiateAccount_skipsWhenExists(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + var httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); + var sdkConfiguration = new LocalSdkConfiguration(wmRuntimeInfo.getHttpBaseUrl()); + Automaton.initializeAutomaton(httpClient, sdkConfiguration); + var sut = Automaton.getInstance(); + + var getUrl = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + + // Existing RG → GET returns 200, so no POST should be issued + stubFor(get(getUrl) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.PERSONAL, UUID.randomUUID(), SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + + + var accountAggregate = sut.getAccountBuilder() + .withResourceGroup(SHORT_NAME, DISPLAY_NAME) + .build(); + + sut.instantiate(accountAggregate); + + verify(1, getRequestedFor(getUrl)); + verify(0, postRequestedFor(getUrl)); + } + + @Test + public void instantiateOrganization_skipsWhenExists(WireMockRuntimeInfo wmRuntimeInfo) throws Exception { + var httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); + var sdkConfiguration = new LocalSdkConfiguration(wmRuntimeInfo.getHttpBaseUrl()); + Automaton.initializeAutomaton(httpClient, sdkConfiguration); + var sut = Automaton.getInstance(); + + var orgId = java.util.UUID.randomUUID(); + var path = String.format("/accounts/organizations/%s/resourcegroups/%s", orgId, SHORT_NAME); + var getUrl = urlPathMatching(path); + + // Existing org RG → GET returns 200, so no POST should be issued + stubFor(get(getUrl) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(""" + {"Id":"%s/%s/%s","DisplayName":"%s","Status":"%s"} + """.formatted(ResourceGroupType.ORGANIZATIONAL, UUID.randomUUID(), SHORT_NAME, DISPLAY_NAME, EntityStatus.ACTIVE)))); + + + var orgAggregate = sut.getOrganizationBuilder() + .withResourceGroup(orgId, SHORT_NAME, DISPLAY_NAME) + .build(); + + sut.instantiate(orgAggregate); + + verify(1, getRequestedFor(getUrl)); + verify(0, postRequestedFor(getUrl)); + } + + @Test + void automaton_provides_account_and_organization_builders() throws Exception { + var httpClient = HttpClient.newHttpClient(); + var sdkConfig = new LocalSdkConfiguration("http://localhost:8080"); + Automaton.initializeAutomaton(httpClient, sdkConfig); + + var automaton = Automaton.getInstance(); + + assertThat(automaton.getAccountBuilder()).isNotNull(); + assertThat(automaton.getAccountBuilder().withResourceGroup(SHORT_NAME, DISPLAY_NAME).build()).isNotNull(); + + assertThat(automaton.getOrganizationBuilder()).isNotNull(); + assertThat(automaton.getOrganizationBuilder() + .withResourceGroup(UUID.randomUUID(), SHORT_NAME, DISPLAY_NAME) + .build()).isNotNull(); + } } From 07457460993830f4b028b458f29be998510f4b2d Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 18 Sep 2025 17:04:27 +0200 Subject: [PATCH 16/18] Fixed URL for accounts --- .../sdk/domain/accounts/service/RestAccountsService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java index 6cf8cfeb..e90874ea 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/domain/accounts/service/RestAccountsService.java @@ -84,7 +84,7 @@ public PersonalResourceGroupResponse getPersonalResourceGroupByShortName(String private URI getPersonalResourceGroupsUri(String shortName) { var path = String.format("%s/%s/", sdkConfiguration.getAccountsEndpoint(), - "accounts/resourcegroups"); // or your existing segment if already correct + "resourcegroups"); // or your existing segment if already correct return URI.create(path + shortName); } From 3ddcad9b6a47156c1c6f8681e579d508453ce63f Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 18 Sep 2025 19:16:30 +0200 Subject: [PATCH 17/18] Fixed URL for accounts tests --- .../com/yanchware/fractal/sdk/AutomatonTest.java | 4 ++-- .../sdk/domain/account/RestAccountsServiceTest.java | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/AutomatonTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/AutomatonTest.java index 9c26cd9d..90161c17 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/AutomatonTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/AutomatonTest.java @@ -68,7 +68,7 @@ public void instantiateAccount_createsPersonalResourceGroup_whenNotExists(WireMo Automaton.initializeAutomaton(httpClient, sdkConfiguration); var sut = Automaton.getInstance(); - var getUrl = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var getUrl = urlPathMatching("/accounts/resourcegroups/" + SHORT_NAME); stubFor(get(getUrl).willReturn(aResponse().withStatus(404))); stubFor(post(getUrl) @@ -129,7 +129,7 @@ public void instantiateAccount_skipsWhenExists(WireMockRuntimeInfo wmRuntimeInfo Automaton.initializeAutomaton(httpClient, sdkConfiguration); var sut = Automaton.getInstance(); - var getUrl = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var getUrl = urlPathMatching("/accounts/resourcegroups/" + SHORT_NAME); // Existing RG → GET returns 200, so no POST should be issued stubFor(get(getUrl) diff --git a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java index 21a9c70c..64a0a419 100644 --- a/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java +++ b/fractal.sdk/src/test/java/com/yanchware/fractal/sdk/domain/account/RestAccountsServiceTest.java @@ -35,7 +35,7 @@ void setUp(WireMockRuntimeInfo wm) { @Test void upsertPersonalResourceGroup_postsToCorrectUrl_andReturnsResponse() throws Exception { // Given - var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var urlPattern = urlPathMatching("/accounts/resourcegroups/" + SHORT_NAME); var accountId = UUID.randomUUID().toString(); stubFor(post(urlPattern) .willReturn(aResponse() @@ -56,7 +56,7 @@ void upsertPersonalResourceGroup_postsToCorrectUrl_andReturnsResponse() throws E @Test void upsertPersonalResourceGroup_sendsDisplayNameInBody() throws Exception { // Given - var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var urlPattern = urlPathMatching("/accounts/resourcegroups/" + SHORT_NAME); var accountId = UUID.randomUUID().toString(); stubFor(post(urlPattern) .withRequestBody(matchingJsonPath("$.DisplayName", equalTo(DISPLAY_NAME))) @@ -79,7 +79,7 @@ void upsertPersonalResourceGroup_sendsDisplayNameInBody() throws Exception { @Test void upsertPersonalResourceGroup_throwsInstantiatorException_on500() { // Given - var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var urlPattern = urlPathMatching("/accounts/resourcegroups/" + SHORT_NAME); stubFor(post(urlPattern) .willReturn(aResponse() @@ -97,7 +97,7 @@ void upsertPersonalResourceGroup_throwsInstantiatorException_on500() { void getPersonalResourceGroupByShortName_returnsResponse_on200() throws Exception { // Given var accountId = java.util.UUID.randomUUID(); - var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var urlPattern = urlPathMatching("/accounts/resourcegroups/" + SHORT_NAME); stubFor(get(urlPattern) .willReturn(aResponse() @@ -119,7 +119,7 @@ void getPersonalResourceGroupByShortName_returnsResponse_on200() throws Exceptio @Test void getPersonalResourceGroupByShortName_throwsInstantiatorException_on500() { // Given - var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var urlPattern = urlPathMatching("/accounts/resourcegroups/" + SHORT_NAME); stubFor(get(urlPattern) .willReturn(aResponse() @@ -137,7 +137,7 @@ void getPersonalResourceGroupByShortName_throwsInstantiatorException_on500() { @Test void getPersonalResourceGroupByShortName_returnsNull_on404() throws Exception { // Given - var urlPattern = urlPathMatching("/accounts/accounts/resourcegroups/" + SHORT_NAME); + var urlPattern = urlPathMatching("/accounts/resourcegroups/" + SHORT_NAME); stubFor(get(urlPattern).willReturn(aResponse().withStatus(404))); From 13012a5d0b0468605b9437784164e4b7612c7d2e Mon Sep 17 00:00:00 2001 From: marco Date: Mon, 29 Sep 2025 12:09:28 +0200 Subject: [PATCH 18/18] Fixed URL for accounts tests --- .../fractal/sdk/configuration/EnvVarSdkConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java index f6db5e8e..ea68a1a5 100644 --- a/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java +++ b/fractal.sdk/src/main/java/com/yanchware/fractal/sdk/configuration/EnvVarSdkConfiguration.java @@ -42,7 +42,7 @@ public EnvVarSdkConfiguration() throws URISyntaxException { DEFAULT_LIVESYSTEM_ENDPOINT = new URI("https://api.fractal.cloud/livesystems"); DEFAULT_BLUEPRINT_ENDPOINT = new URI("https://api.fractal.cloud/blueprints"); DEFAULT_ENVIRONMENTS_ENDPOINT = new URI("https://api.fractal.cloud/environments"); - DEFAULT_ACCOUNTS_ENDPOINT = new URI("https://api.fractal.cloud/accounts"); + DEFAULT_ACCOUNTS_ENDPOINT = new URI("http://localhost:5001/accounts"); } /**