609 lines
17 KiB
Go
609 lines
17 KiB
Go
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 ✓")
|
|
}
|