first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

@@ -0,0 +1,608 @@
package governance
import (
"testing"
"time"
)
// TestInMemorySyncVirtualKeyUpdate tests that in-memory store is updated when VK is updated in DB
func TestInMemorySyncVirtualKeyUpdate(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create a VK with initial budget
vkName := "test-vk-sync-" + generateRandomID()
initialBudget := 10.0
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
Budget: &BudgetRequest{
MaxLimit: initialBudget,
ResetDuration: "1h",
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
t.Logf("Created VK %s with initial budget $%.2f", vkName, initialBudget)
// Verify in-memory store has the VK
getDataResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
if getDataResp.StatusCode != 200 {
t.Fatalf("Failed to get governance data: status %d", getDataResp.StatusCode)
}
virtualKeysMap := getDataResp.Body["virtual_keys"].(map[string]interface{})
// Check that VK exists in in-memory store
vkData, exists := virtualKeysMap[vkValue]
if !exists {
t.Fatalf("VK %s not found in in-memory store after creation", vkValue)
}
vkDataMap := vkData.(map[string]interface{})
vkID2, _ := vkDataMap["id"].(string)
if vkID2 != vkID {
t.Fatalf("VK ID mismatch in in-memory store: expected %s, got %s", vkID, vkID2)
}
t.Logf("VK found in in-memory store after creation ✓")
// Update VK budget to 20.0
newBudget := 20.0
updateResp := MakeRequest(t, APIRequest{
Method: "PUT",
Path: "/api/governance/virtual-keys/" + vkID,
Body: UpdateVirtualKeyRequest{
Budget: &UpdateBudgetRequest{
MaxLimit: &newBudget,
},
},
})
if updateResp.StatusCode != 200 {
t.Fatalf("Failed to update VK: status %d, body: %v", updateResp.StatusCode, updateResp.Body)
}
t.Logf("Updated VK budget from $%.2f to $%.2f", initialBudget, newBudget)
// Verify in-memory store is updated
time.Sleep(500 * time.Millisecond) // Small delay for async updates
getVKResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
if getVKResp2.StatusCode != 200 {
t.Fatalf("Failed to get governance data after update: status %d", getVKResp2.StatusCode)
}
virtualKeysMap2 := getVKResp2.Body["virtual_keys"].(map[string]interface{})
getBudgetsResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap2 := getBudgetsResp2.Body["budgets"].(map[string]interface{})
// Check that VK still exists
vkData2, exists := virtualKeysMap2[vkValue]
if !exists {
t.Fatalf("VK %s not found in in-memory store after update", vkValue)
}
vkDataMap2 := vkData2.(map[string]interface{})
budgetID, _ := vkDataMap2["budget_id"].(string)
// Check that budget in in-memory store is updated
if budgetID != "" {
budgetData, budgetExists := budgetsMap2[budgetID]
if !budgetExists {
t.Fatalf("Budget %s not found in in-memory store", budgetID)
}
budgetDataMap := budgetData.(map[string]interface{})
maxLimit, _ := budgetDataMap["max_limit"].(float64)
if maxLimit != newBudget {
t.Fatalf("Budget max_limit not updated in in-memory store: expected %.2f, got %.2f", newBudget, maxLimit)
}
}
t.Logf("VK budget updated in in-memory store ✓")
}
// TestInMemorySyncTeamUpdate tests that in-memory store is updated when Team is updated
func TestInMemorySyncTeamUpdate(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create a team with initial budget
teamName := "test-team-sync-" + generateRandomID()
initialBudget := 50.0
createTeamResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/teams",
Body: CreateTeamRequest{
Name: teamName,
Budgets: []BudgetRequest{{
MaxLimit: initialBudget,
ResetDuration: "1h",
}},
},
})
if createTeamResp.StatusCode != 200 {
t.Fatalf("Failed to create team: status %d", createTeamResp.StatusCode)
}
teamID := ExtractIDFromResponse(t, createTeamResp)
testData.AddTeam(teamID)
t.Logf("Created team %s with initial budget $%.2f", teamName, initialBudget)
// Verify in-memory store has the team
getDataResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/teams?from_memory=true",
})
if getDataResp.StatusCode != 200 {
t.Fatalf("Failed to get governance data: status %d", getDataResp.StatusCode)
}
teamsMap := getDataResp.Body["teams"].(map[string]interface{})
_, exists := teamsMap[teamID]
if !exists {
t.Fatalf("Team %s not found in in-memory store after creation", teamID)
}
t.Logf("Team found in in-memory store after creation ✓")
// Update team budget to 100.0
newTeamBudget := 100.0
updateResp := MakeRequest(t, APIRequest{
Method: "PUT",
Path: "/api/governance/teams/" + teamID,
Body: UpdateTeamRequest{
Budgets: &[]BudgetRequest{{
MaxLimit: newTeamBudget,
ResetDuration: "1h",
}},
},
})
if updateResp.StatusCode != 200 {
t.Fatalf("Failed to update team: status %d", updateResp.StatusCode)
}
t.Logf("Updated team budget from $%.2f to $%.2f", initialBudget, newTeamBudget)
// Verify in-memory store is updated
time.Sleep(500 * time.Millisecond)
getTeamsResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/teams?from_memory=true",
})
if getTeamsResp2.StatusCode != 200 {
t.Fatalf("Failed to get governance data after update: status %d", getTeamsResp2.StatusCode)
}
teamsMap2 := getTeamsResp2.Body["teams"].(map[string]interface{})
getBudgetsResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap2 := getBudgetsResp2.Body["budgets"].(map[string]interface{})
teamData2, exists := teamsMap2[teamID]
if !exists {
t.Fatalf("Team %s not found in in-memory store after update", teamID)
}
teamDataMap := teamData2.(map[string]interface{})
// Teams now expose a `budgets` array instead of a single `budget_id` — read the first.
var budgetID string
if budgetsList, ok := teamDataMap["budgets"].([]interface{}); ok && len(budgetsList) > 0 {
if b, ok := budgetsList[0].(map[string]interface{}); ok {
budgetID, _ = b["id"].(string)
}
}
if budgetID != "" {
budgetData, budgetExists := budgetsMap2[budgetID]
if !budgetExists {
t.Fatalf("Budget %s not found in in-memory store", budgetID)
}
budgetDataMap := budgetData.(map[string]interface{})
maxLimit, _ := budgetDataMap["max_limit"].(float64)
if maxLimit != newTeamBudget {
t.Fatalf("Team budget max_limit not updated in in-memory store: expected %.2f, got %.2f", newTeamBudget, maxLimit)
}
}
t.Logf("Team budget updated in in-memory store ✓")
}
// TestInMemorySyncCustomerUpdate tests that in-memory store is updated when Customer is updated
func TestInMemorySyncCustomerUpdate(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create a customer with initial budget
customerName := "test-customer-sync-" + generateRandomID()
initialBudget := 100.0
createCustomerResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/customers",
Body: CreateCustomerRequest{
Name: customerName,
Budget: &BudgetRequest{
MaxLimit: initialBudget,
ResetDuration: "1h",
},
},
})
if createCustomerResp.StatusCode != 200 {
t.Fatalf("Failed to create customer: status %d", createCustomerResp.StatusCode)
}
customerID := ExtractIDFromResponse(t, createCustomerResp)
testData.AddCustomer(customerID)
t.Logf("Created customer %s with initial budget $%.2f", customerName, initialBudget)
// Verify in-memory store has the customer
getDataResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/customers?from_memory=true",
})
if getDataResp.StatusCode != 200 {
t.Fatalf("Failed to get governance data: status %d", getDataResp.StatusCode)
}
customersMap := getDataResp.Body["customers"].(map[string]interface{})
_, exists := customersMap[customerID]
if !exists {
t.Fatalf("Customer %s not found in in-memory store after creation", customerID)
}
t.Logf("Customer found in in-memory store after creation ✓")
// Update customer budget to 250.0
newCustomerBudget := 250.0
updateResp := MakeRequest(t, APIRequest{
Method: "PUT",
Path: "/api/governance/customers/" + customerID,
Body: UpdateCustomerRequest{
Budget: &UpdateBudgetRequest{
MaxLimit: &newCustomerBudget,
},
},
})
if updateResp.StatusCode != 200 {
t.Fatalf("Failed to update customer: status %d", updateResp.StatusCode)
}
t.Logf("Updated customer budget from $%.2f to $%.2f", initialBudget, newCustomerBudget)
// Verify in-memory store is updated
time.Sleep(500 * time.Millisecond)
getCustomersResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/customers?from_memory=true",
})
if getCustomersResp2.StatusCode != 200 {
t.Fatalf("Failed to get governance data after update: status %d", getCustomersResp2.StatusCode)
}
customersMap2 := getCustomersResp2.Body["customers"].(map[string]interface{})
getBudgetsResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/budgets?from_memory=true",
})
budgetsMap2 := getBudgetsResp2.Body["budgets"].(map[string]interface{})
customerData2, exists := customersMap2[customerID]
if !exists {
t.Fatalf("Customer %s not found in in-memory store after update", customerID)
}
customerDataMap := customerData2.(map[string]interface{})
budgetID, _ := customerDataMap["budget_id"].(string)
if budgetID != "" {
budgetData, budgetExists := budgetsMap2[budgetID]
if !budgetExists {
t.Fatalf("Budget %s not found in in-memory store", budgetID)
}
budgetDataMap := budgetData.(map[string]interface{})
maxLimit, _ := budgetDataMap["max_limit"].(float64)
if maxLimit != newCustomerBudget {
t.Fatalf("Customer budget max_limit not updated in in-memory store: expected %.2f, got %.2f", newCustomerBudget, maxLimit)
}
}
t.Logf("Customer budget updated in in-memory store ✓")
}
// TestInMemorySyncVirtualKeyDelete tests that in-memory store is updated when VK is deleted
func TestInMemorySyncVirtualKeyDelete(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create a VK
vkName := "test-vk-delete-" + generateRandomID()
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
Budget: &BudgetRequest{
MaxLimit: 10.0,
ResetDuration: "1h",
},
},
})
if createVKResp.StatusCode != 200 {
t.Fatalf("Failed to create VK: status %d", createVKResp.StatusCode)
}
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
vk := createVKResp.Body["virtual_key"].(map[string]interface{})
vkValue := vk["value"].(string)
// Verify in-memory store has the VK (poll to ensure sync completed)
vkExists := WaitForCondition(t, func() bool {
getDataResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
if getDataResp.StatusCode != 200 {
return false
}
virtualKeysMap, ok := getDataResp.Body["virtual_keys"].(map[string]interface{})
if !ok {
return false
}
_, exists := virtualKeysMap[vkValue]
return exists
}, 5*time.Second, "VK exists in in-memory store after creation")
if !vkExists {
t.Fatalf("VK not found in in-memory store after creation (timeout after 5s)")
}
t.Logf("VK found in in-memory store after creation ✓")
// Delete the VK
deleteResp := MakeRequest(t, APIRequest{
Method: "DELETE",
Path: "/api/governance/virtual-keys/" + vkID,
})
if deleteResp.StatusCode != 200 {
t.Fatalf("Failed to delete VK: status %d", deleteResp.StatusCode)
}
t.Logf("Deleted VK from database")
// Verify in-memory store is updated (poll with timeout instead of fixed sleep)
vkRemoved := WaitForCondition(t, func() bool {
getDataResp2 := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
if getDataResp2.StatusCode != 200 {
return false
}
virtualKeysMap2, ok := getDataResp2.Body["virtual_keys"].(map[string]interface{})
if !ok {
return false
}
_, exists := virtualKeysMap2[vkValue]
return !exists // Return true when VK is NOT found (successfully removed)
}, 5*time.Second, "VK removed from in-memory store after deletion")
if !vkRemoved {
t.Fatalf("VK %s still exists in in-memory store after deletion (timeout after 5s)", vkValue)
}
t.Logf("VK removed from in-memory store ✓")
}
// TestDataEndpointConsistency tests that governance endpoints return consistent data
func TestDataEndpointConsistency(t *testing.T) {
t.Parallel()
testData := NewGlobalTestData()
defer testData.Cleanup(t)
// Create multiple resources
vkName := "test-vk-consistency-" + generateRandomID()
createVKResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/virtual-keys",
Body: CreateVirtualKeyRequest{
Name: vkName,
Budget: &BudgetRequest{
MaxLimit: 15.0,
ResetDuration: "1h",
},
},
})
vkID := ExtractIDFromResponse(t, createVKResp)
testData.AddVirtualKey(vkID)
teamName := "test-team-consistency-" + generateRandomID()
createTeamResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/teams",
Body: CreateTeamRequest{
Name: teamName,
Budgets: []BudgetRequest{{
MaxLimit: 30.0,
ResetDuration: "1h",
}},
},
})
teamID := ExtractIDFromResponse(t, createTeamResp)
testData.AddTeam(teamID)
customerName := "test-customer-consistency-" + generateRandomID()
createCustomerResp := MakeRequest(t, APIRequest{
Method: "POST",
Path: "/api/governance/customers",
Body: CreateCustomerRequest{
Name: customerName,
Budget: &BudgetRequest{
MaxLimit: 60.0,
ResetDuration: "1h",
},
},
})
customerID := ExtractIDFromResponse(t, createCustomerResp)
testData.AddCustomer(customerID)
// Wait for all resources to be available in in-memory store
allResourcesReady := WaitForCondition(t, func() bool {
getVKResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
if getVKResp.StatusCode != 200 {
return false
}
getTeamsResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/teams?from_memory=true",
})
if getTeamsResp.StatusCode != 200 {
return false
}
getCustomersResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/customers?from_memory=true",
})
return getCustomersResp.StatusCode == 200
}, 3*time.Second, "all resources available in in-memory store")
if !allResourcesReady {
t.Fatalf("Resources not available in in-memory store (timeout after 3s)")
}
// Get data from separate endpoints
getVKResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys?from_memory=true",
})
if getVKResp.StatusCode != 200 {
t.Fatalf("Failed to get virtual keys: status %d", getVKResp.StatusCode)
}
getTeamsResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/teams?from_memory=true",
})
if getTeamsResp.StatusCode != 200 {
t.Fatalf("Failed to get teams: status %d", getTeamsResp.StatusCode)
}
getCustomersResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/customers?from_memory=true",
})
if getCustomersResp.StatusCode != 200 {
t.Fatalf("Failed to get customers: status %d", getCustomersResp.StatusCode)
}
virtualKeysMap := getVKResp.Body["virtual_keys"].(map[string]interface{})
teamsMap := getTeamsResp.Body["teams"].(map[string]interface{})
customersMap := getCustomersResp.Body["customers"].(map[string]interface{})
// Verify all created resources are in the in-memory data
vkCount := len(virtualKeysMap)
teamCount := len(teamsMap)
customerCount := len(customersMap)
if vkCount == 0 {
t.Fatalf("No virtual keys found in data endpoint")
}
if teamCount == 0 {
t.Fatalf("No teams found in data endpoint")
}
if customerCount == 0 {
t.Fatalf("No customers found in data endpoint")
}
t.Logf("Data endpoint returned consistent data: %d VKs, %d teams, %d customers ✓", vkCount, teamCount, customerCount)
// Get the individual endpoints and verify consistency
getVKsResp := MakeRequest(t, APIRequest{
Method: "GET",
Path: "/api/governance/virtual-keys",
})
if getVKsResp.StatusCode != 200 {
t.Fatalf("Failed to get virtual keys: status %d", getVKsResp.StatusCode)
}
vksFromEndpoint, _ := getVKsResp.Body["count"].(float64)
if int(vksFromEndpoint) != vkCount {
// Can fail because sqlite db might get locked because of all parallel tests
t.Logf("[WARN]VK count mismatch between /data endpoint and /virtual-keys endpoint: %d vs %d (this can happen because of parallel tests)", vkCount, int(vksFromEndpoint))
}
t.Logf("Data consistency verified between endpoints ✓")
}