package main import ( "testing" "atlas9.dev/c/core" "atlas9.dev/c/core/iam" ) func TestAddTenantMember(t *testing.T) { s := setupTestServer(t) userID := core.NewID() createTestUser(t, s.DB, userID) tenant := saveTenant(t, s.URL, "Test Org", iam.TenantOrganization) mustRPC[AddTenantMemberReq, AddTenantMemberRes](t, s.URL, "/tenant-members/add", AddTenantMemberReq{ TenantID: tenant.ID, UserID: userID, Status: iam.MemberInvited, }) res := mustRPC[ListTenantMembersReq, ListTenantMembersRes](t, s.URL, "/tenant-members/list-by-tenant", ListTenantMembersReq{ TenantID: tenant.ID, }) if len(res.Items) != 1 { t.Fatalf("expected 1 member, got %d", len(res.Items)) } if res.Items[0].Status != iam.MemberInvited { t.Errorf("status = %q, want %q", res.Items[0].Status, iam.MemberInvited) } } func TestAddTenantMemberIdempotent(t *testing.T) { s := setupTestServer(t) userID := core.NewID() createTestUser(t, s.DB, userID) tenant := saveTenant(t, s.URL, "Test Org", iam.TenantOrganization) mustRPC[AddTenantMemberReq, AddTenantMemberRes](t, s.URL, "/tenant-members/add", AddTenantMemberReq{ TenantID: tenant.ID, UserID: userID, Status: iam.MemberInvited, }) mustRPC[AddTenantMemberReq, AddTenantMemberRes](t, s.URL, "/tenant-members/add", AddTenantMemberReq{ TenantID: tenant.ID, UserID: userID, Status: iam.MemberActive, }) res := mustRPC[ListTenantMembersReq, ListTenantMembersRes](t, s.URL, "/tenant-members/list-by-tenant", ListTenantMembersReq{ TenantID: tenant.ID, }) if len(res.Items) != 1 { t.Fatalf("expected 1 member, got %d", len(res.Items)) } // Should keep original status due to ON CONFLICT DO NOTHING if res.Items[0].Status != iam.MemberInvited { t.Errorf("status = %q, want %q (idempotent)", res.Items[0].Status, iam.MemberInvited) } } func TestSetTenantMemberStatus(t *testing.T) { s := setupTestServer(t) userID := core.NewID() createTestUser(t, s.DB, userID) tenant := saveTenant(t, s.URL, "Test Org", iam.TenantOrganization) mustRPC[AddTenantMemberReq, AddTenantMemberRes](t, s.URL, "/tenant-members/add", AddTenantMemberReq{ TenantID: tenant.ID, UserID: userID, Status: iam.MemberInvited, }) mustRPC[SetTenantMemberStatusReq, SetTenantMemberStatusRes](t, s.URL, "/tenant-members/set-status", SetTenantMemberStatusReq{ TenantID: tenant.ID, UserID: userID, Status: iam.MemberActive, }) got := mustRPC[GetTenantMemberReq, iam.TenantMember](t, s.URL, "/tenant-members/get", GetTenantMemberReq{ TenantID: tenant.ID, UserID: userID, }) if got.Status != iam.MemberActive { t.Errorf("status = %q, want %q", got.Status, iam.MemberActive) } } func TestRemoveTenantMember(t *testing.T) { s := setupTestServer(t) userID := core.NewID() createTestUser(t, s.DB, userID) tenant := saveTenant(t, s.URL, "Test Org", iam.TenantOrganization) mustRPC[AddTenantMemberReq, AddTenantMemberRes](t, s.URL, "/tenant-members/add", AddTenantMemberReq{ TenantID: tenant.ID, UserID: userID, Status: iam.MemberActive, }) mustRPC[RemoveTenantMemberReq, RemoveTenantMemberRes](t, s.URL, "/tenant-members/remove", RemoveTenantMemberReq{ TenantID: tenant.ID, UserID: userID, }) res := mustRPC[ListTenantMembersReq, ListTenantMembersRes](t, s.URL, "/tenant-members/list-by-tenant", ListTenantMembersReq{ TenantID: tenant.ID, }) if len(res.Items) != 0 { t.Errorf("expected 0 members, got %d", len(res.Items)) } } func TestCannotRemoveOwnerFromPersonalTenant(t *testing.T) { s := setupTestServer(t) userID := core.NewID() createTestUser(t, s.DB, userID) // Create a personal tenant with ID == userID mustRPC[iam.Tenant, iam.Tenant](t, s.URL, "/tenants/save", iam.Tenant{ ID: userID, Name: "Personal", Type: iam.TenantPersonal, }) mustRPC[AddTenantMemberReq, AddTenantMemberRes](t, s.URL, "/tenant-members/add", AddTenantMemberReq{ TenantID: userID, UserID: userID, Status: iam.MemberActive, }) // Removing owner from personal tenant should fail r := rpcCall[RemoveTenantMemberReq, RemoveTenantMemberRes](t, s.URL, "/tenant-members/remove", RemoveTenantMemberReq{ TenantID: userID, UserID: userID, }) if r.Error == "" { t.Error("expected error removing owner from personal tenant") } // Membership should still exist res := mustRPC[ListTenantMembersReq, ListTenantMembersRes](t, s.URL, "/tenant-members/list-by-tenant", ListTenantMembersReq{ TenantID: userID, }) if len(res.Items) != 1 { t.Errorf("expected 1 member, got %d", len(res.Items)) } } func TestListUserTenants(t *testing.T) { s := setupTestServer(t) userID := core.NewID() createTestUser(t, s.DB, userID) t1 := saveTenant(t, s.URL, "Org A", iam.TenantOrganization) t2 := saveTenant(t, s.URL, "Org B", iam.TenantOrganization) mustRPC[AddTenantMemberReq, AddTenantMemberRes](t, s.URL, "/tenant-members/add", AddTenantMemberReq{ TenantID: t1.ID, UserID: userID, Status: iam.MemberActive, }) mustRPC[AddTenantMemberReq, AddTenantMemberRes](t, s.URL, "/tenant-members/add", AddTenantMemberReq{ TenantID: t2.ID, UserID: userID, Status: iam.MemberActive, }) res := mustRPC[ListUserTenantsReq, ListUserTenantsRes](t, s.URL, "/tenant-members/list-by-user", ListUserTenantsReq{ UserID: userID, }) if len(res.Items) != 2 { t.Errorf("expected 2 tenants, got %d", len(res.Items)) } }