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
286 lines
8.6 KiB
Go
286 lines
8.6 KiB
Go
package customer
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/damirmukimov/city_resource_graph/models/params"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestCalculateCustomerMetrics(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
year int
|
|
totalOrgs int
|
|
payingShare float64
|
|
expectedTotal int
|
|
expectedPaying int
|
|
expectedFree int
|
|
expectedShare float64
|
|
}{
|
|
{
|
|
name: "Year 1 - 500 orgs, 30% paying",
|
|
year: 1,
|
|
totalOrgs: 500,
|
|
payingShare: 0.30,
|
|
expectedTotal: 500,
|
|
expectedPaying: 150,
|
|
expectedFree: 350,
|
|
expectedShare: 0.30,
|
|
},
|
|
{
|
|
name: "Year 2 - 2000 orgs, 30% paying",
|
|
year: 2,
|
|
totalOrgs: 2000,
|
|
payingShare: 0.30,
|
|
expectedTotal: 2000,
|
|
expectedPaying: 600,
|
|
expectedFree: 1400,
|
|
expectedShare: 0.30,
|
|
},
|
|
{
|
|
name: "Year 3 - 5000 orgs, 30% paying",
|
|
year: 3,
|
|
totalOrgs: 5000,
|
|
payingShare: 0.30,
|
|
expectedTotal: 5000,
|
|
expectedPaying: 1500,
|
|
expectedFree: 3500,
|
|
expectedShare: 0.30,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := ¶ms.Params{
|
|
Adoption: params.AdoptionParams{
|
|
TotalOrgs: params.YearlyInt{params.YearKey(tt.year): tt.totalOrgs},
|
|
PayingShare: params.YearlyFloat{params.YearKey(tt.year): tt.payingShare},
|
|
},
|
|
}
|
|
|
|
result := CalculateCustomerMetrics(tt.year, p)
|
|
|
|
assert.Equal(t, tt.expectedTotal, result.TotalOrgs, "Total orgs should match")
|
|
assert.Equal(t, tt.expectedPaying, result.PayingOrgs, "Paying orgs should match")
|
|
assert.Equal(t, tt.expectedFree, result.FreeOrgs, "Free orgs should match")
|
|
assert.Equal(t, tt.expectedShare, result.PayingShare, "Paying share should match")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCalculateTierDistribution(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
payingOrgs int
|
|
tierMix params.TierMix
|
|
expectedBasic int
|
|
expectedBusiness int
|
|
expectedEnterprise int
|
|
expectedTotal int
|
|
}{
|
|
{
|
|
name: "Year 1 tier distribution - 150 paying orgs",
|
|
payingOrgs: 150,
|
|
tierMix: params.TierMix{Basic: 0.60, Business: 0.30, Enterprise: 0.10},
|
|
expectedBasic: 90, // 150 * 0.60
|
|
expectedBusiness: 45, // 150 * 0.30
|
|
expectedEnterprise: 15, // 150 * 0.10
|
|
expectedTotal: 150,
|
|
},
|
|
{
|
|
name: "Year 3 tier distribution - 1500 paying orgs",
|
|
payingOrgs: 1500,
|
|
tierMix: params.TierMix{Basic: 0.54, Business: 0.38, Enterprise: 0.08},
|
|
expectedBasic: 810, // 1500 * 0.54
|
|
expectedBusiness: 570, // 1500 * 0.38
|
|
expectedEnterprise: 120, // 1500 * 0.08
|
|
expectedTotal: 1500,
|
|
},
|
|
{
|
|
name: "Rounding adjustment needed",
|
|
payingOrgs: 10,
|
|
tierMix: params.TierMix{Basic: 0.60, Business: 0.30, Enterprise: 0.10},
|
|
expectedBasic: 6, // 10 * 0.60 = 6
|
|
expectedBusiness: 3, // 10 * 0.30 = 3
|
|
expectedEnterprise: 1, // 10 * 0.10 = 1
|
|
expectedTotal: 10,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := ¶ms.Params{
|
|
Pricing: params.PricingParams{
|
|
TierMix: params.YearlyTierMix{"1": tt.tierMix},
|
|
},
|
|
}
|
|
|
|
result := CalculateTierDistribution(1, tt.payingOrgs, p)
|
|
|
|
assert.Equal(t, tt.expectedBasic, result.Basic, "Basic tier should match")
|
|
assert.Equal(t, tt.expectedBusiness, result.Business, "Business tier should match")
|
|
assert.Equal(t, tt.expectedEnterprise, result.Enterprise, "Enterprise tier should match")
|
|
assert.Equal(t, tt.expectedTotal, result.Total, "Total should match")
|
|
|
|
// Verify rounding adjustment works
|
|
total := result.Basic + result.Business + result.Enterprise
|
|
assert.Equal(t, tt.payingOrgs, total, "Distribution should sum to total paying orgs")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultChurnMetrics(t *testing.T) {
|
|
churn := DefaultChurnMetrics()
|
|
|
|
// Basic tier
|
|
assert.Equal(t, 0.15, churn.Basic.AnnualChurn, "Basic churn rate should be 0.15")
|
|
assert.Equal(t, 0.85, churn.Basic.Retention, "Basic retention should be 0.85")
|
|
assert.Equal(t, 48, churn.Basic.AvgLifetimeMonths, "Basic lifetime should be 48 months")
|
|
|
|
// Business tier
|
|
assert.Equal(t, 0.10, churn.Business.AnnualChurn, "Business churn rate should be 0.10")
|
|
assert.Equal(t, 0.90, churn.Business.Retention, "Business retention should be 0.90")
|
|
assert.Equal(t, 64, churn.Business.AvgLifetimeMonths, "Business lifetime should be 64 months")
|
|
|
|
// Enterprise tier
|
|
assert.Equal(t, 0.05, churn.Enterprise.AnnualChurn, "Enterprise churn rate should be 0.05")
|
|
assert.Equal(t, 0.95, churn.Enterprise.Retention, "Enterprise retention should be 0.95")
|
|
assert.Equal(t, 80, churn.Enterprise.AvgLifetimeMonths, "Enterprise lifetime should be 80 months")
|
|
}
|
|
|
|
// Test customer metrics with edge cases for 100% coverage
|
|
func TestCalculateCustomerMetrics_EdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
year int
|
|
totalOrgs int
|
|
payingShare float64
|
|
expectedTotal int
|
|
expectedPaying int
|
|
expectedFree int
|
|
expectedShare float64
|
|
}{
|
|
{
|
|
name: "Zero total orgs",
|
|
year: 1,
|
|
totalOrgs: 0,
|
|
payingShare: 0.3,
|
|
expectedTotal: 0,
|
|
expectedPaying: 0,
|
|
expectedFree: 0,
|
|
expectedShare: 0.3,
|
|
},
|
|
{
|
|
name: "100% paying share",
|
|
year: 1,
|
|
totalOrgs: 100,
|
|
payingShare: 1.0,
|
|
expectedTotal: 100,
|
|
expectedPaying: 100,
|
|
expectedFree: 0,
|
|
expectedShare: 1.0,
|
|
},
|
|
{
|
|
name: "0% paying share",
|
|
year: 1,
|
|
totalOrgs: 100,
|
|
payingShare: 0.0,
|
|
expectedTotal: 100,
|
|
expectedPaying: 0,
|
|
expectedFree: 100,
|
|
expectedShare: 0.0,
|
|
},
|
|
{
|
|
name: "Non-integer paying orgs (rounding)",
|
|
year: 1,
|
|
totalOrgs: 10,
|
|
payingShare: 0.33, // 10 * 0.33 = 3.3, should round to 3
|
|
expectedTotal: 10,
|
|
expectedPaying: 3,
|
|
expectedFree: 7,
|
|
expectedShare: 0.33,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := ¶ms.Params{
|
|
Adoption: params.AdoptionParams{
|
|
TotalOrgs: params.YearlyInt{params.YearKey(tt.year): tt.totalOrgs},
|
|
PayingShare: params.YearlyFloat{params.YearKey(tt.year): tt.payingShare},
|
|
},
|
|
}
|
|
|
|
result := CalculateCustomerMetrics(tt.year, p)
|
|
|
|
assert.Equal(t, tt.expectedTotal, result.TotalOrgs, "Total orgs should match")
|
|
assert.Equal(t, tt.expectedPaying, result.PayingOrgs, "Paying orgs should match")
|
|
assert.Equal(t, tt.expectedFree, result.FreeOrgs, "Free orgs should match")
|
|
assert.Equal(t, tt.expectedShare, result.PayingShare, "Paying share should match")
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test tier distribution with edge cases
|
|
func TestCalculateTierDistribution_EdgeCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
payingOrgs int
|
|
tierMix params.TierMix
|
|
expectedBasic int
|
|
expectedBusiness int
|
|
expectedEnterprise int
|
|
expectedTotal int
|
|
}{
|
|
{
|
|
name: "Zero paying orgs",
|
|
payingOrgs: 0,
|
|
tierMix: params.TierMix{Basic: 0.6, Business: 0.3, Enterprise: 0.1},
|
|
expectedBasic: 0,
|
|
expectedBusiness: 0,
|
|
expectedEnterprise: 0,
|
|
expectedTotal: 0,
|
|
},
|
|
{
|
|
name: "Single paying org - basic tier",
|
|
payingOrgs: 1,
|
|
tierMix: params.TierMix{Basic: 0.6, Business: 0.3, Enterprise: 0.1},
|
|
expectedBasic: 1, // Gets the rounding adjustment
|
|
expectedBusiness: 0,
|
|
expectedEnterprise: 0,
|
|
expectedTotal: 1,
|
|
},
|
|
{
|
|
name: "Uneven distribution requiring rounding",
|
|
payingOrgs: 7,
|
|
tierMix: params.TierMix{Basic: 0.5, Business: 0.3, Enterprise: 0.2},
|
|
expectedBasic: 4, // 7 * 0.5 = 3.5, rounded to 4 with adjustment
|
|
expectedBusiness: 2, // 7 * 0.3 = 2.1, floored to 2
|
|
expectedEnterprise: 1, // 7 * 0.2 = 1.4, floored to 1
|
|
expectedTotal: 7,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := ¶ms.Params{
|
|
Pricing: params.PricingParams{
|
|
TierMix: params.YearlyTierMix{"1": tt.tierMix},
|
|
},
|
|
}
|
|
|
|
result := CalculateTierDistribution(1, tt.payingOrgs, p)
|
|
|
|
assert.Equal(t, tt.expectedBasic, result.Basic, "Basic tier should match")
|
|
assert.Equal(t, tt.expectedBusiness, result.Business, "Business tier should match")
|
|
assert.Equal(t, tt.expectedEnterprise, result.Enterprise, "Enterprise tier should match")
|
|
assert.Equal(t, tt.expectedTotal, result.Total, "Total should match")
|
|
|
|
// Verify distribution sums correctly
|
|
total := result.Basic + result.Business + result.Enterprise
|
|
assert.Equal(t, tt.payingOrgs, total, "Distribution should sum to total paying orgs")
|
|
})
|
|
}
|
|
}
|