ffcf41b85a
Introduce `AddUserToOrganization`, `RemoveAPIKey`, and `RemoveOrganization` commands to enhance organization management. Implement validation for user addition and API key removal. Update GraphQL schema to support new mutations and add caching for the new events, ensuring that organizations and their relationships are accurately represented in the cache.
658 lines
17 KiB
Go
658 lines
17 KiB
Go
package cache
|
|
|
|
import (
|
|
"log/slog"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
|
|
|
"gitlab.com/unboundsoftware/schemas/domain"
|
|
"gitlab.com/unboundsoftware/schemas/hash"
|
|
)
|
|
|
|
func TestCache_OrganizationByAPIKey(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
apiKey := "test-api-key-123" // gitleaks:allow
|
|
hashedKey, err := hash.APIKey(apiKey)
|
|
require.NoError(t, err)
|
|
|
|
// Add organization to cache
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Test Org",
|
|
}
|
|
c.organizations[orgID] = org
|
|
|
|
// Add API key to cache
|
|
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{
|
|
Name: "test-key",
|
|
OrganizationId: orgID,
|
|
Key: hashedKey,
|
|
Refs: []string{"main"},
|
|
Read: true,
|
|
Publish: true,
|
|
}
|
|
|
|
// Test finding organization by plaintext API key
|
|
foundOrg := c.OrganizationByAPIKey(apiKey)
|
|
require.NotNil(t, foundOrg)
|
|
assert.Equal(t, org.Name, foundOrg.Name)
|
|
assert.Equal(t, orgID, foundOrg.ID.String())
|
|
|
|
// Test with wrong API key
|
|
notFoundOrg := c.OrganizationByAPIKey("wrong-key")
|
|
assert.Nil(t, notFoundOrg)
|
|
}
|
|
|
|
func TestCache_OrganizationByAPIKey_Legacy(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
apiKey := "legacy-api-key-456" // gitleaks:allow
|
|
legacyHash := hash.String(apiKey)
|
|
|
|
// Add organization to cache
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Legacy Org",
|
|
}
|
|
c.organizations[orgID] = org
|
|
|
|
// Add API key with legacy SHA256 hash
|
|
c.apiKeys[apiKeyId(orgID, "legacy-key")] = domain.APIKey{
|
|
Name: "legacy-key",
|
|
OrganizationId: orgID,
|
|
Key: legacyHash,
|
|
Refs: []string{"main"},
|
|
Read: true,
|
|
Publish: false,
|
|
}
|
|
|
|
// Test finding organization with legacy hash
|
|
foundOrg := c.OrganizationByAPIKey(apiKey)
|
|
require.NotNil(t, foundOrg)
|
|
assert.Equal(t, org.Name, foundOrg.Name)
|
|
}
|
|
|
|
func TestCache_OrganizationsByUser(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
userSub := "user-123"
|
|
org1ID := uuid.New().String()
|
|
org2ID := uuid.New().String()
|
|
|
|
org1 := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(org1ID),
|
|
Name: "Org 1",
|
|
}
|
|
org2 := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(org2ID),
|
|
Name: "Org 2",
|
|
}
|
|
|
|
c.organizations[org1ID] = org1
|
|
c.organizations[org2ID] = org2
|
|
c.users[userSub] = []string{org1ID, org2ID}
|
|
|
|
orgs := c.OrganizationsByUser(userSub)
|
|
assert.Len(t, orgs, 2)
|
|
assert.Contains(t, []string{orgs[0].Name, orgs[1].Name}, "Org 1")
|
|
assert.Contains(t, []string{orgs[0].Name, orgs[1].Name}, "Org 2")
|
|
}
|
|
|
|
func TestCache_ApiKeyByKey(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
apiKey := "test-api-key-789" // gitleaks:allow
|
|
hashedKey, err := hash.APIKey(apiKey)
|
|
require.NoError(t, err)
|
|
|
|
expectedKey := domain.APIKey{
|
|
Name: "test-key",
|
|
OrganizationId: orgID,
|
|
Key: hashedKey,
|
|
Refs: []string{"main", "dev"},
|
|
Read: true,
|
|
Publish: true,
|
|
}
|
|
|
|
c.apiKeys[apiKeyId(orgID, "test-key")] = expectedKey
|
|
|
|
foundKey := c.ApiKeyByKey(apiKey)
|
|
require.NotNil(t, foundKey)
|
|
assert.Equal(t, expectedKey.Name, foundKey.Name)
|
|
assert.Equal(t, expectedKey.OrganizationId, foundKey.OrganizationId)
|
|
assert.Equal(t, expectedKey.Refs, foundKey.Refs)
|
|
|
|
// Test with wrong key
|
|
notFoundKey := c.ApiKeyByKey("wrong-key")
|
|
assert.Nil(t, notFoundKey)
|
|
}
|
|
|
|
func TestCache_Services(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
ref := "main"
|
|
service1 := "service-1"
|
|
service2 := "service-2"
|
|
lastUpdate := "2024-01-01T12:00:00Z"
|
|
|
|
c.services[orgID] = map[string]map[string]struct{}{
|
|
ref: {
|
|
service1: {},
|
|
service2: {},
|
|
},
|
|
}
|
|
c.lastUpdate[refKey(orgID, ref)] = lastUpdate
|
|
|
|
// Test getting services with empty lastUpdate
|
|
services, returnedLastUpdate := c.Services(orgID, ref, "")
|
|
assert.Len(t, services, 2)
|
|
assert.Contains(t, services, service1)
|
|
assert.Contains(t, services, service2)
|
|
assert.Equal(t, lastUpdate, returnedLastUpdate)
|
|
|
|
// Test with older lastUpdate (should return services)
|
|
services, returnedLastUpdate = c.Services(orgID, ref, "2023-12-31T12:00:00Z")
|
|
assert.Len(t, services, 2)
|
|
assert.Equal(t, lastUpdate, returnedLastUpdate)
|
|
|
|
// Test with newer lastUpdate (should return empty)
|
|
services, returnedLastUpdate = c.Services(orgID, ref, "2024-01-02T12:00:00Z")
|
|
assert.Len(t, services, 0)
|
|
assert.Equal(t, lastUpdate, returnedLastUpdate)
|
|
}
|
|
|
|
func TestCache_SubGraphId(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
ref := "main"
|
|
service := "test-service"
|
|
subGraphID := uuid.New().String()
|
|
|
|
c.subGraphs[subGraphKey(orgID, ref, service)] = subGraphID
|
|
|
|
foundID := c.SubGraphId(orgID, ref, service)
|
|
assert.Equal(t, subGraphID, foundID)
|
|
|
|
// Test with non-existent key
|
|
notFoundID := c.SubGraphId("wrong-org", ref, service)
|
|
assert.Empty(t, notFoundID)
|
|
}
|
|
|
|
func TestCache_Update_OrganizationAdded(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
event := &domain.OrganizationAdded{
|
|
Name: "New Org",
|
|
Initiator: "user-123",
|
|
}
|
|
event.ID = *eventsourced.IdFromString(orgID)
|
|
|
|
_, err := c.Update(event, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Verify organization was added
|
|
org, exists := c.organizations[orgID]
|
|
assert.True(t, exists)
|
|
assert.Equal(t, "New Org", org.Name)
|
|
|
|
// Verify user was added
|
|
assert.Contains(t, c.users["user-123"], orgID)
|
|
}
|
|
|
|
func TestCache_Update_APIKeyAdded(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
keyName := "test-key"
|
|
hashedKey := "hashed-key-value"
|
|
|
|
// Add organization first
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Test Org",
|
|
APIKeys: []domain.APIKey{},
|
|
}
|
|
c.organizations[orgID] = org
|
|
|
|
event := &domain.APIKeyAdded{
|
|
OrganizationId: orgID,
|
|
Name: keyName,
|
|
Key: hashedKey,
|
|
Refs: []string{"main"},
|
|
Read: true,
|
|
Publish: false,
|
|
Initiator: "user-123",
|
|
}
|
|
event.ID = *eventsourced.IdFromString(uuid.New().String())
|
|
|
|
_, err := c.Update(event, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Verify API key was added to cache
|
|
key, exists := c.apiKeys[apiKeyId(orgID, keyName)]
|
|
assert.True(t, exists)
|
|
assert.Equal(t, keyName, key.Name)
|
|
assert.Equal(t, hashedKey, key.Key)
|
|
assert.Equal(t, []string{"main"}, key.Refs)
|
|
|
|
// Verify API key was added to organization
|
|
updatedOrg := c.organizations[orgID]
|
|
assert.Len(t, updatedOrg.APIKeys, 1)
|
|
assert.Equal(t, keyName, updatedOrg.APIKeys[0].Name)
|
|
}
|
|
|
|
func TestCache_Update_SubGraphUpdated(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
ref := "main"
|
|
service := "test-service"
|
|
subGraphID := uuid.New().String()
|
|
|
|
event := &domain.SubGraphUpdated{
|
|
OrganizationId: orgID,
|
|
Ref: ref,
|
|
Service: service,
|
|
Initiator: "user-123",
|
|
}
|
|
event.ID = *eventsourced.IdFromString(subGraphID)
|
|
event.SetWhen(time.Now())
|
|
|
|
_, err := c.Update(event, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Verify subgraph was added to services
|
|
assert.Contains(t, c.services[orgID][ref], subGraphID)
|
|
|
|
// Verify subgraph ID was stored
|
|
assert.Equal(t, subGraphID, c.subGraphs[subGraphKey(orgID, ref, service)])
|
|
|
|
// Verify lastUpdate was set
|
|
assert.NotEmpty(t, c.lastUpdate[refKey(orgID, ref)])
|
|
}
|
|
|
|
func TestCache_AddUser_NoDuplicates(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
userSub := "user-123"
|
|
orgID := uuid.New().String()
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Test Org",
|
|
}
|
|
|
|
// Add user first time
|
|
c.addUser(userSub, org)
|
|
assert.Len(t, c.users[userSub], 1)
|
|
assert.Equal(t, orgID, c.users[userSub][0])
|
|
|
|
// Add same user/org again - should not create duplicate
|
|
c.addUser(userSub, org)
|
|
assert.Len(t, c.users[userSub], 1, "Should not add duplicate organization")
|
|
assert.Equal(t, orgID, c.users[userSub][0])
|
|
}
|
|
|
|
func TestCache_ConcurrentReads(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
// Setup test data
|
|
orgID := uuid.New().String()
|
|
apiKey := "test-concurrent-key" // gitleaks:allow
|
|
hashedKey, err := hash.APIKey(apiKey)
|
|
require.NoError(t, err)
|
|
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Concurrent Test Org",
|
|
}
|
|
c.organizations[orgID] = org
|
|
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{
|
|
Name: "test-key",
|
|
OrganizationId: orgID,
|
|
Key: hashedKey,
|
|
}
|
|
|
|
// Run concurrent reads (reduced for race detector)
|
|
var wg sync.WaitGroup
|
|
numGoroutines := 20
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
org := c.OrganizationByAPIKey(apiKey)
|
|
assert.NotNil(t, org)
|
|
assert.Equal(t, "Concurrent Test Org", org.Name)
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestCache_ConcurrentWrites(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
var wg sync.WaitGroup
|
|
numGoroutines := 10 // Reduced for race detector
|
|
|
|
// Concurrent organization additions
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(1)
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
orgID := uuid.New().String()
|
|
event := &domain.OrganizationAdded{
|
|
Name: "Org " + string(rune(index)),
|
|
Initiator: "user-" + string(rune(index)),
|
|
}
|
|
event.ID = *eventsourced.IdFromString(orgID)
|
|
_, err := c.Update(event, nil)
|
|
assert.NoError(t, err)
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Verify all organizations were added
|
|
assert.Equal(t, numGoroutines, len(c.organizations))
|
|
}
|
|
|
|
func TestCache_ConcurrentReadsAndWrites(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
// Setup initial data
|
|
orgID := uuid.New().String()
|
|
apiKey := "test-rw-key" // gitleaks:allow
|
|
hashedKey, err := hash.APIKey(apiKey)
|
|
require.NoError(t, err)
|
|
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "RW Test Org",
|
|
}
|
|
c.organizations[orgID] = org
|
|
c.apiKeys[apiKeyId(orgID, "test-key")] = domain.APIKey{
|
|
Name: "test-key",
|
|
OrganizationId: orgID,
|
|
Key: hashedKey,
|
|
}
|
|
c.users["user-initial"] = []string{orgID}
|
|
|
|
var wg sync.WaitGroup
|
|
numReaders := 10 // Reduced for race detector
|
|
numWriters := 5 // Reduced for race detector
|
|
iterations := 3 // Reduced for race detector
|
|
|
|
// Concurrent readers
|
|
for i := 0; i < numReaders; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < iterations; j++ {
|
|
org := c.OrganizationByAPIKey(apiKey)
|
|
assert.NotNil(t, org)
|
|
orgs := c.OrganizationsByUser("user-initial")
|
|
assert.NotEmpty(t, orgs)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Concurrent writers
|
|
for i := 0; i < numWriters; i++ {
|
|
wg.Add(1)
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
newOrgID := uuid.New().String()
|
|
event := &domain.OrganizationAdded{
|
|
Name: "New Org " + string(rune(index)),
|
|
Initiator: "user-new-" + string(rune(index)),
|
|
}
|
|
event.ID = *eventsourced.IdFromString(newOrgID)
|
|
_, err := c.Update(event, nil)
|
|
assert.NoError(t, err)
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Verify cache is in consistent state
|
|
assert.GreaterOrEqual(t, len(c.organizations), numWriters)
|
|
}
|
|
|
|
func TestCache_Update_APIKeyRemoved(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
keyName := "test-key"
|
|
hashedKey := "hashed-key-value"
|
|
|
|
// Add organization with API key
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Test Org",
|
|
APIKeys: []domain.APIKey{
|
|
{
|
|
Name: keyName,
|
|
OrganizationId: orgID,
|
|
Key: hashedKey,
|
|
Refs: []string{"main"},
|
|
Read: true,
|
|
Publish: false,
|
|
},
|
|
},
|
|
}
|
|
c.organizations[orgID] = org
|
|
c.apiKeys[apiKeyId(orgID, keyName)] = org.APIKeys[0]
|
|
|
|
// Verify key exists before removal
|
|
_, exists := c.apiKeys[apiKeyId(orgID, keyName)]
|
|
assert.True(t, exists)
|
|
|
|
// Remove the API key
|
|
event := &domain.APIKeyRemoved{
|
|
KeyName: keyName,
|
|
Initiator: "user-123",
|
|
}
|
|
event.ID = *eventsourced.IdFromString(orgID)
|
|
|
|
_, err := c.Update(event, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Verify API key was removed from cache
|
|
_, exists = c.apiKeys[apiKeyId(orgID, keyName)]
|
|
assert.False(t, exists, "API key should be removed from cache")
|
|
|
|
// Verify API key was removed from organization
|
|
updatedOrg := c.organizations[orgID]
|
|
assert.Len(t, updatedOrg.APIKeys, 0, "API key should be removed from organization")
|
|
}
|
|
|
|
func TestCache_Update_APIKeyRemoved_MultipleKeys(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
|
|
// Add organization with multiple API keys
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Test Org",
|
|
APIKeys: []domain.APIKey{
|
|
{
|
|
Name: "key1",
|
|
OrganizationId: orgID,
|
|
Key: "hash1",
|
|
},
|
|
{
|
|
Name: "key2",
|
|
OrganizationId: orgID,
|
|
Key: "hash2",
|
|
},
|
|
{
|
|
Name: "key3",
|
|
OrganizationId: orgID,
|
|
Key: "hash3",
|
|
},
|
|
},
|
|
}
|
|
c.organizations[orgID] = org
|
|
c.apiKeys[apiKeyId(orgID, "key1")] = org.APIKeys[0]
|
|
c.apiKeys[apiKeyId(orgID, "key2")] = org.APIKeys[1]
|
|
c.apiKeys[apiKeyId(orgID, "key3")] = org.APIKeys[2]
|
|
|
|
// Remove the middle key
|
|
event := &domain.APIKeyRemoved{
|
|
KeyName: "key2",
|
|
Initiator: "user-123",
|
|
}
|
|
event.ID = *eventsourced.IdFromString(orgID)
|
|
|
|
_, err := c.Update(event, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Verify only key2 was removed
|
|
_, exists := c.apiKeys[apiKeyId(orgID, "key1")]
|
|
assert.True(t, exists, "key1 should still exist")
|
|
|
|
_, exists = c.apiKeys[apiKeyId(orgID, "key2")]
|
|
assert.False(t, exists, "key2 should be removed")
|
|
|
|
_, exists = c.apiKeys[apiKeyId(orgID, "key3")]
|
|
assert.True(t, exists, "key3 should still exist")
|
|
|
|
// Verify organization has 2 keys remaining
|
|
updatedOrg := c.organizations[orgID]
|
|
assert.Len(t, updatedOrg.APIKeys, 2)
|
|
assert.Equal(t, "key1", updatedOrg.APIKeys[0].Name)
|
|
assert.Equal(t, "key3", updatedOrg.APIKeys[1].Name)
|
|
}
|
|
|
|
func TestCache_Update_OrganizationRemoved(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
userSub := "user-123"
|
|
|
|
// Add organization with API keys, users, and subgraphs
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Test Org",
|
|
APIKeys: []domain.APIKey{
|
|
{
|
|
Name: "key1",
|
|
OrganizationId: orgID,
|
|
Key: "hash1",
|
|
},
|
|
},
|
|
}
|
|
c.organizations[orgID] = org
|
|
c.apiKeys[apiKeyId(orgID, "key1")] = org.APIKeys[0]
|
|
c.users[userSub] = []string{orgID}
|
|
c.services[orgID] = map[string]map[string]struct{}{
|
|
"main": {
|
|
"service1": {},
|
|
},
|
|
}
|
|
c.subGraphs[subGraphKey(orgID, "main", "service1")] = "subgraph-id"
|
|
c.lastUpdate[refKey(orgID, "main")] = "2024-01-01T12:00:00Z"
|
|
|
|
// Remove the organization
|
|
event := &domain.OrganizationRemoved{
|
|
Initiator: userSub,
|
|
}
|
|
event.ID = *eventsourced.IdFromString(orgID)
|
|
|
|
_, err := c.Update(event, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Verify organization was removed
|
|
_, exists := c.organizations[orgID]
|
|
assert.False(t, exists, "Organization should be removed from cache")
|
|
|
|
// Verify API keys were removed
|
|
_, exists = c.apiKeys[apiKeyId(orgID, "key1")]
|
|
assert.False(t, exists, "API keys should be removed from cache")
|
|
|
|
// Verify user association was removed
|
|
userOrgs := c.users[userSub]
|
|
assert.NotContains(t, userOrgs, orgID, "User should not be associated with removed organization")
|
|
|
|
// Verify services were removed
|
|
_, exists = c.services[orgID]
|
|
assert.False(t, exists, "Services should be removed from cache")
|
|
|
|
// Verify subgraphs were removed
|
|
_, exists = c.subGraphs[subGraphKey(orgID, "main", "service1")]
|
|
assert.False(t, exists, "Subgraphs should be removed from cache")
|
|
|
|
// Verify lastUpdate was removed
|
|
_, exists = c.lastUpdate[refKey(orgID, "main")]
|
|
assert.False(t, exists, "LastUpdate should be removed from cache")
|
|
}
|
|
|
|
func TestCache_Update_OrganizationRemoved_MultipleUsers(t *testing.T) {
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
|
c := New(logger)
|
|
|
|
orgID := uuid.New().String()
|
|
user1 := "user-1"
|
|
user2 := "user-2"
|
|
otherOrgID := uuid.New().String()
|
|
|
|
// Add organization
|
|
org := domain.Organization{
|
|
BaseAggregate: eventsourced.BaseAggregateFromString(orgID),
|
|
Name: "Test Org",
|
|
}
|
|
c.organizations[orgID] = org
|
|
|
|
// Add users with multiple org associations
|
|
c.users[user1] = []string{orgID, otherOrgID}
|
|
c.users[user2] = []string{orgID}
|
|
|
|
// Remove the organization
|
|
event := &domain.OrganizationRemoved{
|
|
Initiator: user1,
|
|
}
|
|
event.ID = *eventsourced.IdFromString(orgID)
|
|
|
|
_, err := c.Update(event, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Verify user1 still has otherOrgID but not removed orgID
|
|
assert.Len(t, c.users[user1], 1)
|
|
assert.Equal(t, otherOrgID, c.users[user1][0])
|
|
|
|
// Verify user2 has no organizations
|
|
assert.Len(t, c.users[user2], 0)
|
|
}
|