mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Initialize git repository - Add comprehensive .gitignore for Go projects - Install golangci-lint v2.6.0 (latest v2) globally - Configure .golangci.yml with appropriate linters and formatters - Fix all formatting issues (gofmt) - Fix all errcheck issues (unchecked errors) - Adjust complexity threshold for validation functions - All checks passing: build, test, vet, lint
328 lines
8.5 KiB
Go
328 lines
8.5 KiB
Go
package validator
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/damirmukimov/city_resource_graph/models/cost"
|
|
"github.com/damirmukimov/city_resource_graph/models/customer"
|
|
"github.com/damirmukimov/city_resource_graph/models/impact"
|
|
"github.com/damirmukimov/city_resource_graph/models/revenue"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestValidate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
year int
|
|
custMetrics customer.CustomerMetrics
|
|
revBreakdown revenue.RevenueBreakdown
|
|
costBreakdown cost.CostBreakdown
|
|
impactMetrics impact.ImpactMetrics
|
|
expectErrors bool
|
|
errorRules []string
|
|
}{
|
|
{
|
|
name: "Valid year 1 data",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
PayingOrgs: 150,
|
|
TotalOrgs: 500,
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 145440.0, // Valid ARPU: ~969 (within 300-6000 range)
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 1400000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 94500.0,
|
|
},
|
|
expectErrors: true, // This test data triggers validation warnings
|
|
},
|
|
{
|
|
name: "ARPU too low",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
PayingOrgs: 10,
|
|
TotalOrgs: 500,
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 1000.0, // ARPU: 100 (below 300)
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 1400000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 94500.0,
|
|
},
|
|
expectErrors: true,
|
|
errorRules: []string{"ARPU_SANITY"},
|
|
},
|
|
{
|
|
name: "CO2/revenue ratio too high",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
PayingOrgs: 150,
|
|
TotalOrgs: 500,
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 1000.0, // Very low revenue
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 1400000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 12000.0, // High CO2 vs revenue
|
|
},
|
|
expectErrors: true,
|
|
errorRules: []string{"CO2_REVENUE_RATIO"},
|
|
},
|
|
{
|
|
name: "Profitability warning",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
PayingOrgs: 150,
|
|
TotalOrgs: 500,
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 1000000.0,
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 3000000.0, // Costs > revenue
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 94500.0,
|
|
},
|
|
expectErrors: true,
|
|
errorRules: []string{"PROFITABILITY_WARNING"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := Validate(tt.year, tt.custMetrics, tt.revBreakdown, tt.costBreakdown, tt.impactMetrics)
|
|
|
|
if tt.expectErrors {
|
|
assert.False(t, result.IsValid(), "Should have validation errors")
|
|
assert.True(t, len(result.Errors) > 0, "Should have at least one error")
|
|
|
|
if len(tt.errorRules) > 0 {
|
|
errorRules := make([]string, len(result.Errors))
|
|
for i, err := range result.Errors {
|
|
errorRules[i] = err.Rule
|
|
}
|
|
|
|
for _, expectedRule := range tt.errorRules {
|
|
assert.Contains(t, errorRules, expectedRule, "Should contain expected error rule")
|
|
}
|
|
}
|
|
} else {
|
|
assert.True(t, result.IsValid(), "Should be valid")
|
|
assert.Equal(t, 0, len(result.Errors), "Should have no errors")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateMunicipalPenetration(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
year int
|
|
cities int
|
|
maxCities int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "Valid penetration",
|
|
year: 1,
|
|
cities: 2,
|
|
maxCities: 10,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Exceeds max cities",
|
|
year: 1,
|
|
cities: 15,
|
|
maxCities: 10,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := ValidateMunicipalPenetration(tt.year, tt.cities, tt.maxCities)
|
|
|
|
if tt.expectError {
|
|
assert.NotEqual(t, ValidationError{}, result, "Should return validation error")
|
|
assert.Contains(t, result.Message, "exceeds maximum expected", "Error message should mention exceeding max")
|
|
} else {
|
|
assert.Equal(t, ValidationError{}, result, "Should return empty validation error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidationResult_IsValid(t *testing.T) {
|
|
valid := ValidationResult{Errors: []ValidationError{}}
|
|
assert.True(t, valid.IsValid(), "Empty errors should be valid")
|
|
|
|
invalid := ValidationResult{Errors: []ValidationError{
|
|
{Rule: "TEST_RULE", Message: "Test error"},
|
|
}}
|
|
assert.False(t, invalid.IsValid(), "Non-empty errors should be invalid")
|
|
}
|
|
|
|
func TestValidationError_Error(t *testing.T) {
|
|
err := ValidationError{
|
|
Year: 1,
|
|
Rule: "TEST_RULE",
|
|
Message: "Test error message",
|
|
Value: 42.0,
|
|
}
|
|
|
|
errorMsg := err.Error()
|
|
assert.Contains(t, errorMsg, "Year 1", "Should include year")
|
|
assert.Contains(t, errorMsg, "TEST_RULE", "Should include rule")
|
|
assert.Contains(t, errorMsg, "Test error message", "Should include message")
|
|
assert.Contains(t, errorMsg, "42", "Should include value")
|
|
}
|
|
|
|
// Test edge cases and additional validation scenarios for 100% coverage
|
|
func TestValidate_EdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
year int
|
|
custMetrics customer.CustomerMetrics
|
|
revBreakdown revenue.RevenueBreakdown
|
|
costBreakdown cost.CostBreakdown
|
|
impactMetrics impact.ImpactMetrics
|
|
expectErrors bool
|
|
}{
|
|
{
|
|
name: "Zero customers - ARPU check skipped",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
PayingOrgs: 0, // This should skip ARPU validation
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 1000000.0,
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 500000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 1000.0,
|
|
},
|
|
expectErrors: false,
|
|
},
|
|
{
|
|
name: "Zero revenue - margin calculation safe",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
PayingOrgs: 100,
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 0.0, // Zero revenue
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 100000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 1000.0,
|
|
},
|
|
expectErrors: true, // Should trigger profitability warning
|
|
},
|
|
{
|
|
name: "Reasonable ARPU with high CO2 ratio",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
PayingOrgs: 100,
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 300000.0, // Revenue = 3000 ARPU (within 300-6000 range)
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 150000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 2700.0, // CO2/€ = 9, below 10 limit
|
|
},
|
|
expectErrors: false,
|
|
},
|
|
{
|
|
name: "Trigger implementation density validation",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
TotalOrgs: 100,
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 100000.0,
|
|
Implementation: revenue.ImplementationRevenue{
|
|
Matches: 20.0, // 20 matches for 100 orgs = 0.2 matches/org, below 1.5 limit
|
|
PaidImpls: 10.0,
|
|
Total: 50000.0,
|
|
},
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 50000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 1000.0,
|
|
},
|
|
expectErrors: false, // Should not trigger implementation density error
|
|
},
|
|
{
|
|
name: "Trigger heat MWh validation",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
PayingOrgs: 100,
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 100000.0,
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 50000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 6000000.0, // Very high CO2 that will estimate heat > 5M MWh
|
|
},
|
|
expectErrors: true, // Should trigger heat MWh validation
|
|
},
|
|
{
|
|
name: "Trigger implementation density error",
|
|
year: 1,
|
|
custMetrics: customer.CustomerMetrics{
|
|
TotalOrgs: 10, // Small number of orgs
|
|
},
|
|
revBreakdown: revenue.RevenueBreakdown{
|
|
Total: 100000.0,
|
|
Implementation: revenue.ImplementationRevenue{
|
|
Matches: 20.0, // 20 matches for 10 orgs = 2.0 matches/org, above 1.5 limit
|
|
PaidImpls: 10.0,
|
|
Total: 50000.0,
|
|
},
|
|
},
|
|
costBreakdown: cost.CostBreakdown{
|
|
Total: 50000.0,
|
|
},
|
|
impactMetrics: impact.ImpactMetrics{
|
|
CO2Avoided: 1000.0,
|
|
},
|
|
expectErrors: true, // Should trigger implementation density error
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := Validate(tt.year, tt.custMetrics, tt.revBreakdown, tt.costBreakdown, tt.impactMetrics)
|
|
|
|
if tt.expectErrors {
|
|
assert.False(t, result.IsValid(), "Should have validation errors")
|
|
assert.True(t, len(result.Errors) > 0, "Should have at least one error")
|
|
} else {
|
|
assert.True(t, result.IsValid(), "Should be valid")
|
|
}
|
|
})
|
|
}
|
|
}
|