package app import ( "bytes" "strings" "testing" "golang.org/x/crypto/bcrypt" ) func productionConfigForTest() Config { return Config{ Port: 8888, DSN: "xingyu:password@tcp(mysql:3306)/im", JWTSecret: "0123456789abcdef0123456789abcdef", ConfigEncryptionKey: "abcdef0123456789abcdef0123456789", Environment: "production", AllowedOrigins: []string{"https://app.example.com", "https://admin.example.com"}, } } func TestValidateProductionConfig(t *testing.T) { config := productionConfigForTest() if err := validateConfig(config); err != nil { t.Fatalf("expected a valid production config: %v", err) } tests := []struct { name string mutate func(*Config) }{ {"root database account", func(config *Config) { config.DSN = "root:root@tcp(mysql:3306)/im" }}, {"weak jwt", func(config *Config) { config.JWTSecret = "short" }}, {"missing encryption key", func(config *Config) { config.ConfigEncryptionKey = "" }}, {"wildcard cors", func(config *Config) { config.AllowedOrigins = []string{"*"} }}, {"insecure origin", func(config *Config) { config.AllowedOrigins = []string{"http://app.example.com"} }}, {"demo seed", func(config *Config) { config.SeedDemo = true }}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { invalid := config test.mutate(&invalid) if err := validateConfig(invalid); err == nil { t.Fatal("expected production validation to reject config") } }) } } func TestPhoneEncryptionRoundTrip(t *testing.T) { application := &App{config: productionConfigForTest()} phone := "13800138000" first, err := application.encryptPhone(phone) if err != nil { t.Fatal(err) } second, err := application.encryptPhone(phone) if err != nil { t.Fatal(err) } if bytes.Equal(first, second) { t.Fatal("phone encryption must use a unique random nonce") } decrypted, err := application.decryptPhone(first) if err != nil || decrypted != phone { t.Fatalf("unexpected decrypted phone %q: %v", decrypted, err) } } func TestProductionURLValidation(t *testing.T) { if !validHTTPSURL("https://pay.example.com/create") { t.Fatal("expected HTTPS URL to be accepted") } for _, value := range []string{"http://pay.example.com", "javascript:alert(1)", "https:///missing-host", ""} { if validHTTPSURL(value) { t.Fatalf("expected URL to be rejected: %s", value) } } } func TestPasswordAndPhoneRules(t *testing.T) { if validUserPassword("") { t.Fatal("an account must still have a password") } if !validPhone("13800138000") || validPhone("23800138000") || validPhone("1380013800x") { t.Fatal("phone policy is not enforced") } } func TestPasswordsWithoutStrengthRestrictions(t *testing.T) { for _, password := range []string{"1", "123456", "a", "password", "!", "中", " ", strings.Repeat("a", 72), strings.Repeat("a", 73), strings.Repeat("密码", 100)} { if !validUserPassword(password) { t.Fatalf("password with %d bytes was rejected by policy", len(password)) } hash, err := hashPassword(password) if err != nil || !checkPassword(hash, password) { t.Fatalf("password with %d bytes cannot be stored and verified: %v", len(password), err) } if checkPassword(hash, password+"x") || checkPassword(hash, "") { t.Fatal("password comparison ignored a suffix or accepted an empty password") } config := productionConfigForTest() config.BootstrapAdminPassword = password if err := validateConfig(config); err != nil { t.Fatal("bootstrap admin still enforces password strength:", err) } } } func TestLongPasswordHashesAndLegacyCompatibility(t *testing.T) { legacyPassword := strings.Repeat("x", 72) legacy, err := bcrypt.GenerateFromPassword([]byte(legacyPassword), bcrypt.MinCost) if err != nil || !checkPassword(string(legacy), legacyPassword) || checkPassword(string(legacy), legacyPassword+"suffix") { t.Fatal("legacy bcrypt credentials must work without accepting truncated input") } password := legacyPassword + "a" first, err := hashPassword(password) if err != nil { t.Fatal(err) } second, err := hashPassword(password) if err != nil { t.Fatal(err) } if first == second || !strings.HasPrefix(first, longPasswordHashPrefix) || len(first) > 255 { t.Fatal("long passwords require unique salts and a hash that fits existing storage") } if !checkPassword(first, password) || checkPassword(first, legacyPassword+"b") || checkPassword(first, legacyPassword) { t.Fatal("the entire long password must participate in verification") } for _, invalid := range []string{"", "plaintext", "$argon2id$", strings.Replace(first, "m=19456", "m=999999999", 1), first[:len(first)-1], longPasswordHashPrefix + strings.Repeat("!", 66)} { if checkPassword(invalid, password) { t.Fatal("malformed hash was accepted") } } }