feat: create initial admin user + add vendor to new tenant orgs
All checks were successful
CI / build (push) Successful in 50s
CI / docker (push) Successful in 41s

When creating a tenant, the vendor can specify adminUsername +
adminPassword. The backend creates the user in Logto and assigns them
the owner org role. The vendor user is also auto-added to every new
org for support access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-10 07:35:17 +02:00
parent b7a0530466
commit 2dc75c4361
10 changed files with 114 additions and 22 deletions

View File

@@ -35,7 +35,7 @@ class LicenseControllerTest {
private String createTenantAndGetId() throws Exception {
String slug = "license-tenant-" + System.nanoTime();
var request = new CreateTenantRequest("License Test Org", slug, "MID");
var request = new CreateTenantRequest("License Test Org", slug, "MID", null, null);
var result = mockMvc.perform(post("/api/tenants")
.with(jwt().jwt(j -> j

View File

@@ -35,7 +35,7 @@ class TenantControllerTest {
@Test
void createTenant_returns201() throws Exception {
var request = new CreateTenantRequest("Test Org", "test-org-" + System.nanoTime(), "LOW");
var request = new CreateTenantRequest("Test Org", "test-org-" + System.nanoTime(), "LOW", null, null);
mockMvc.perform(post("/api/tenants")
.with(jwt().jwt(j -> j
@@ -54,7 +54,7 @@ class TenantControllerTest {
@Test
void createTenant_returns409ForDuplicateSlug() throws Exception {
String slug = "duplicate-slug-" + System.nanoTime();
var request = new CreateTenantRequest("First", slug, null);
var request = new CreateTenantRequest("First", slug, null, null, null);
mockMvc.perform(post("/api/tenants")
.with(jwt().jwt(j -> j
@@ -77,7 +77,7 @@ class TenantControllerTest {
@Test
void createTenant_returns401WithoutToken() throws Exception {
var request = new CreateTenantRequest("Test", "no-auth-test", null);
var request = new CreateTenantRequest("Test", "no-auth-test", null, null, null);
mockMvc.perform(post("/api/tenants")
.contentType(MediaType.APPLICATION_JSON)
@@ -88,7 +88,7 @@ class TenantControllerTest {
@Test
void getTenant_returnsTenantById() throws Exception {
String slug = "get-test-" + System.nanoTime();
var request = new CreateTenantRequest("Get Test", slug, null);
var request = new CreateTenantRequest("Get Test", slug, null, null, null);
var createResult = mockMvc.perform(post("/api/tenants")
.with(jwt().jwt(j -> j

View File

@@ -41,7 +41,7 @@ class TenantServiceTest {
@Test
void create_savesNewTenantWithCorrectFields() {
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "MID");
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "MID", null, null);
var actorId = UUID.randomUUID();
when(tenantRepository.existsBySlugAndStatusNot("acme-corp", TenantStatus.DELETED)).thenReturn(false);
@@ -57,7 +57,7 @@ class TenantServiceTest {
@Test
void create_throwsForDuplicateSlug() {
var request = new CreateTenantRequest("Acme Corp", "acme-corp", null);
var request = new CreateTenantRequest("Acme Corp", "acme-corp", null, null, null);
when(tenantRepository.existsBySlugAndStatusNot("acme-corp", TenantStatus.DELETED)).thenReturn(true);
@@ -68,7 +68,7 @@ class TenantServiceTest {
@Test
void create_logsAuditEvent() {
var request = new CreateTenantRequest("Acme Corp", "acme-corp", null);
var request = new CreateTenantRequest("Acme Corp", "acme-corp", null, null, null);
var actorId = UUID.randomUUID();
when(tenantRepository.existsBySlugAndStatusNot("acme-corp", TenantStatus.DELETED)).thenReturn(false);
@@ -83,7 +83,7 @@ class TenantServiceTest {
@Test
void create_defaultsToLowTier() {
var request = new CreateTenantRequest("Acme Corp", "acme-corp", null);
var request = new CreateTenantRequest("Acme Corp", "acme-corp", null, null, null);
when(tenantRepository.existsBySlugAndStatusNot("acme-corp", TenantStatus.DELETED)).thenReturn(false);
when(tenantRepository.save(any(TenantEntity.class))).thenAnswer(inv -> inv.getArgument(0));

View File

@@ -34,7 +34,7 @@ class VendorTenantControllerTest {
private ObjectMapper objectMapper;
private String createTenant(String name, String slug, String tier) throws Exception {
var request = new CreateTenantRequest(name, slug, tier);
var request = new CreateTenantRequest(name, slug, tier, null, null);
var result = mockMvc.perform(post("/api/vendor/tenants")
.with(jwt().jwt(j -> j
.claim("sub", "test-user")
@@ -65,7 +65,7 @@ class VendorTenantControllerTest {
@Test
void createTenant_returns201() throws Exception {
String slug = "create-test-" + System.nanoTime();
var request = new CreateTenantRequest("Create Test Org", slug, "MID");
var request = new CreateTenantRequest("Create Test Org", slug, "MID", null, null);
mockMvc.perform(post("/api/vendor/tenants")
.with(jwt().jwt(j -> j
@@ -87,7 +87,7 @@ class VendorTenantControllerTest {
String slug = "duplicate-vendor-" + System.nanoTime();
createTenant("First Org", slug, "LOW");
var request = new CreateTenantRequest("Second Org", slug, "LOW");
var request = new CreateTenantRequest("Second Org", slug, "LOW", null, null);
mockMvc.perform(post("/api/vendor/tenants")
.with(jwt().jwt(j -> j
.claim("sub", "test-user")
@@ -144,7 +144,7 @@ class VendorTenantControllerTest {
@Test
void createTenant_returns401WithoutAuth() throws Exception {
var request = new CreateTenantRequest("No Auth Org", "no-auth-vendor-" + System.nanoTime(), "LOW");
var request = new CreateTenantRequest("No Auth Org", "no-auth-vendor-" + System.nanoTime(), "LOW", null, null);
mockMvc.perform(post("/api/vendor/tenants")
.contentType(MediaType.APPLICATION_JSON)

View File

@@ -96,7 +96,7 @@ class VendorTenantServiceTest {
@Test
void createAndProvision_createsTenantAndLicense() throws Exception {
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "LOW");
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "LOW", null, null);
var actorId = UUID.randomUUID();
var tenant = tenantWithId("Acme Corp", "acme-corp", Tier.LOW);
var license = licenseWithId(tenant.getId());
@@ -116,7 +116,7 @@ class VendorTenantServiceTest {
@Test
void createAndProvision_setsActiveWhenProvisionerSucceeds() throws Exception {
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "LOW");
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "LOW", null, null);
var actorId = UUID.randomUUID();
var tenant = tenantWithId("Acme Corp", "acme-corp", Tier.LOW);
var license = licenseWithId(tenant.getId());
@@ -137,7 +137,7 @@ class VendorTenantServiceTest {
@Test
void createAndProvision_setsProvisionErrorOnFailure() throws Exception {
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "LOW");
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "LOW", null, null);
var actorId = UUID.randomUUID();
var tenant = tenantWithId("Acme Corp", "acme-corp", Tier.LOW);
var license = licenseWithId(tenant.getId());
@@ -157,7 +157,7 @@ class VendorTenantServiceTest {
@Test
void createAndProvision_worksWithoutProvisioner() throws Exception {
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "LOW");
var request = new CreateTenantRequest("Acme Corp", "acme-corp", "LOW", null, null);
var actorId = UUID.randomUUID();
var tenant = tenantWithId("Acme Corp", "acme-corp", Tier.LOW);
var license = licenseWithId(tenant.getId());