package engine import ( "strings" "sync" "testing" ) func TestSecretStore_Add_MinLength(t *testing.T) { s := newSecretStore() // 短于 8 字节 → 错误 if err := s.Add("KEY", "short"); err == nil { t.Error("expected error for short secret value") } // 恰好 8 字节 → 成功 if err := s.Add("KEY", "12345678"); err != nil { t.Errorf("unexpected error: %v", err) } } func TestSecretStore_Redact_Basic(t *testing.T) { s := newSecretStore() _ = s.Add("DB_PASS", "s3cr3tpassword") input := "Connection: host=localhost password=s3cr3tpassword user=admin" got := s.Redact(input) want := "Connection: host=localhost password=[SECRET:DB_PASS] user=admin" if got != want { t.Errorf("Redact: got %q, want %q", got, want) } } func TestSecretStore_Redact_NoMatch(t *testing.T) { s := newSecretStore() _ = s.Add("DB_PASS", "s3cr3tpassword") input := "nothing sensitive here" if got := s.Redact(input); got != input { t.Errorf("Redact modified non-matching string: %q", got) } } func TestSecretStore_Redact_EmptyStore(t *testing.T) { s := newSecretStore() input := "some output with no secrets" if got := s.Redact(input); got != input { t.Error("Redact on empty store should return input unchanged") } } func TestSecretStore_Redact_MultipleSecrets(t *testing.T) { s := newSecretStore() _ = s.Add("TOKEN_A", "tok_abcdefgh") _ = s.Add("TOKEN_B", "tok_12345678") input := "a=tok_abcdefgh b=tok_12345678 c=public" got := s.Redact(input) if strings.Contains(got, "tok_abcdefgh") { t.Error("TOKEN_A value should be redacted") } if strings.Contains(got, "tok_12345678") { t.Error("TOKEN_B value should be redacted") } if !strings.Contains(got, "public") { t.Error("non-secret text should remain") } } func TestSecretStore_Environ(t *testing.T) { s := newSecretStore() _ = s.Add("DB_PASSWORD", "mypassword123") _ = s.Add("API_KEY", "apikey_xyz_abc") envs := s.Environ() if len(envs) != 2 { t.Fatalf("expected 2 env vars, got %d", len(envs)) } // 检查格式 found := map[string]bool{} for _, e := range envs { found[e] = true } if !found["DB_PASSWORD=mypassword123"] { t.Error("DB_PASSWORD not in Environ()") } if !found["API_KEY=apikey_xyz_abc"] { t.Error("API_KEY not in Environ()") } } func TestSecretStore_Len(t *testing.T) { s := newSecretStore() if s.Len() != 0 { t.Error("empty store should have Len 0") } _ = s.Add("K1", "value_one_long") _ = s.Add("K2", "value_two_long") if s.Len() != 2 { t.Errorf("expected Len 2, got %d", s.Len()) } } func TestSecretStore_Overwrite(t *testing.T) { s := newSecretStore() _ = s.Add("TOKEN", "old_value_here") _ = s.Add("TOKEN", "new_value_here") // Redact 应使用新值 if got := s.Redact("old_value_here"); strings.Contains(got, "old_value_here") { // 旧值未注册,应保持原样 } if got := s.Redact("new_value_here"); !strings.Contains(got, "[SECRET:TOKEN]") { t.Errorf("overwritten secret not redacted: %q", got) } // 长度应仍为 1(同名覆盖不新增条目) if s.Len() != 1 { t.Errorf("overwrite should not increase Len, got %d", s.Len()) } } func TestMergeSecrets(t *testing.T) { base := newSecretStore() _ = base.Add("BASE_KEY", "base_secret_val") extras := []secretEntry{ {name: "REQ_KEY", value: "req_secret_val_"}, } merged := mergeSecrets(base, extras) if merged.Len() != 2 { t.Fatalf("expected 2 secrets in merged, got %d", merged.Len()) } // base 不受影响 if base.Len() != 1 { t.Error("merge should not modify base") } // merged 能 redact 两者 out := merged.Redact("base_secret_val and req_secret_val_") if contains(out, "base_secret_val") || contains(out, "req_secret_val_") { t.Errorf("merged redact failed: %q", out) } } func TestMergeSecrets_ExtraOverridesBase(t *testing.T) { base := newSecretStore() _ = base.Add("SHARED", "base_shared_val") extras := []secretEntry{ {name: "SHARED", value: "req_shared_val_"}, } merged := mergeSecrets(base, extras) // extra 覆盖 base if merged.Len() != 1 { t.Errorf("override should not add entry, got Len=%d", merged.Len()) } if got := merged.Redact("req_shared_val_"); !strings.Contains(got, "[SECRET:SHARED]") { t.Errorf("extra override not in merged redact: %q", got) } } func TestSecretStore_ConcurrentAccess(t *testing.T) { s := newSecretStore() _ = s.Add("INIT_KEY_", "init_secret_val") var wg sync.WaitGroup for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() _ = s.Redact("init_secret_val in output") _ = s.Environ() _ = s.Len() }() } wg.Wait() }