turash/models/validator/validator_test.go
Damir Mukimov 4a2fda96cd
Initial commit: Repository setup with .gitignore, golangci-lint v2.6.0, and code quality checks
- 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
2025-11-01 07:36:22 +01:00

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")
}
})
}
}