package sqlite import ( "context" "fmt" "strings" "testing" "time" ) func TestCollisionProbability(t *testing.T) { tests := []struct { numIssues int idLength int expected float64 // approximate }{ {50, 4, 0.0007}, // ~0.07% {500, 4, 0.0717}, // ~7.17% {1000, 5, 0.0082}, // ~0.82% {1000, 6, 0.0002}, // ~0.02% } for _, tt := range tests { got := collisionProbability(tt.numIssues, tt.idLength) // Allow 20% tolerance for approximation (birthday paradox is an approximation) diff := got - tt.expected if diff < 0 { diff = -diff } tolerance := tt.expected * 0.2 if diff > tolerance { t.Errorf("collisionProbability(%d, %d) = %f, want ~%f (diff: %f)", tt.numIssues, tt.idLength, got, tt.expected, diff) } } } func TestComputeAdaptiveLength(t *testing.T) { tests := []struct { name string numIssues int config AdaptiveIDConfig want int }{ { name: "tiny database uses 3 chars", numIssues: 50, config: DefaultAdaptiveConfig(), want: 3, }, { name: "small database uses 4 chars", numIssues: 500, config: DefaultAdaptiveConfig(), want: 4, }, { name: "medium database uses 5 chars", numIssues: 3000, config: DefaultAdaptiveConfig(), want: 5, }, { name: "large database uses 6 chars", numIssues: 20000, config: DefaultAdaptiveConfig(), want: 6, }, { name: "very large database uses 7 chars", numIssues: 100000, config: DefaultAdaptiveConfig(), want: 7, }, { name: "custom threshold - stricter", numIssues: 200, config: AdaptiveIDConfig{ MaxCollisionProbability: 0.01, // 1% threshold MinLength: 3, MaxLength: 8, }, want: 5, }, { name: "custom threshold - more lenient", numIssues: 1000, config: AdaptiveIDConfig{ MaxCollisionProbability: 0.50, // 50% threshold MinLength: 3, MaxLength: 8, }, want: 4, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := computeAdaptiveLength(tt.numIssues, tt.config) if got != tt.want { t.Errorf("computeAdaptiveLength(%d) = %d, want %d", tt.numIssues, got, tt.want) } }) } } func TestGenerateHashID_VariableLengths(t *testing.T) { prefix := "bd" title := "Test issue" description := "Test description" creator := "test@example.com" timestamp, _ := time.Parse(time.RFC3339, "2024-01-01T00:00:00Z") tests := []struct { length int expectedLen int // length of hash portion (without prefix) }{ {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}, {8, 8}, } for _, tt := range tests { t.Run(fmt.Sprintf("length_%d", tt.length), func(t *testing.T) { id := generateHashID(prefix, title, description, creator, timestamp, tt.length, 0) // Format: "bd-xxxx" where xxxx is the hash if !strings.HasPrefix(id, prefix+"-") { t.Errorf("ID should start with %s-, got %s", prefix, id) } hashPart := strings.TrimPrefix(id, prefix+"-") if len(hashPart) != tt.expectedLen { t.Errorf("Hash length = %d, want %d (full ID: %s)", len(hashPart), tt.expectedLen, id) } }) } } func TestGetAdaptiveIDLength_Integration(t *testing.T) { // Use newTestStore for proper test isolation db := newTestStore(t, "") defer db.Close() ctx := context.Background() // Get a dedicated connection for this test conn, err := db.db.Conn(ctx) if err != nil { t.Fatalf("Failed to get connection: %v", err) } defer conn.Close() // Test default config (should use 3 chars for empty database) length, err := GetAdaptiveIDLength(ctx, conn, "test") if err != nil { t.Fatalf("GetAdaptiveIDLength failed: %v", err) } if length != 3 { t.Errorf("Empty database should use 3 chars, got %d", length) } // Test custom config if err := db.SetConfig(ctx, "max_collision_prob", "0.01"); err != nil { t.Fatalf("Failed to set max_collision_prob: %v", err) } if err := db.SetConfig(ctx, "min_hash_length", "5"); err != nil { t.Fatalf("Failed to set min_hash_length: %v", err) } length, err = GetAdaptiveIDLength(ctx, conn, "test") if err != nil { t.Fatalf("GetAdaptiveIDLength with custom config failed: %v", err) } if length < 5 { t.Errorf("With min_hash_length=5, got %d", length) } }