package postgres import ( "bytes" _ "embed" "encoding/json" "errors" "fmt" "os/exec" "sort" "strings" "testing" "time" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers" "github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config" "github.com/trufflesecurity/trufflehog/v3/pkg/context" ) const ( postgresUser = "postgres" postgresPass = "23201da=b56ca236f3dc6736c0f9afad" postgresHost = "localhost" postgresPort = "5434" // Do not use 5433, as local dev environments can use it for other things defaultPort = "5432" ) //go:embed expected_output.json var expectedOutput []byte func TestAnalyzer_Analyze(t *testing.T) { if err := startPostgres(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { t.Fatalf("could not start local postgres: %v w/stderr:\n%s", err, string(exitErr.Stderr)) } else { t.Fatalf("could not start local postgres: %v", err) } } defer stopPostgres() tests := []struct { name string connectionString string want []byte // JSON string wantErr bool }{ { name: "valid Postgres connection", connectionString: fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres`, postgresUser, postgresPass, postgresHost, postgresPort), want: expectedOutput, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a := Analyzer{Cfg: &config.Config{}} got, err := a.Analyze(context.Background(), map[string]string{"connection_string": tt.connectionString}) if (err != nil) != tt.wantErr { t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr) return } // bindings need to be in the same order to be comparable sortBindings(got.Bindings) // Marshal the actual result to JSON gotJSON, err := json.Marshal(got) if err != nil { t.Fatalf("could not marshal got to JSON: %s", err) } // Parse the expected JSON string var wantObj analyzers.AnalyzerResult if err := json.Unmarshal(tt.want, &wantObj); err != nil { t.Fatalf("could not unmarshal want JSON string: %s", err) } // bindings need to be in the same order to be comparable sortBindings(wantObj.Bindings) // Marshal the expected result to JSON (to normalize) wantJSON, err := json.Marshal(wantObj) if err != nil { t.Fatalf("could not marshal want to JSON: %s", err) } // Compare the JSON strings if string(gotJSON) != string(wantJSON) { // Pretty-print both JSON strings for easier comparison var gotIndented, wantIndented []byte gotIndented, err = json.MarshalIndent(got, "", " ") if err != nil { t.Fatalf("could not marshal got to indented JSON: %s", err) } wantIndented, err = json.MarshalIndent(wantObj, "", " ") if err != nil { t.Fatalf("could not marshal want to indented JSON: %s", err) } t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented) } }) } } // Helper function to sort bindings func sortBindings(bindings []analyzers.Binding) { sort.SliceStable(bindings, func(i, j int) bool { if bindings[i].Resource.Name == bindings[j].Resource.Name { return bindings[i].Permission.Value < bindings[j].Permission.Value } return bindings[i].Resource.Name < bindings[j].Resource.Name }) } var postgresDockerHash string func dockerLogLine(hash string, needle string) chan struct{} { ch := make(chan struct{}, 1) go func() { for { out, err := exec.Command("docker", "logs", hash).CombinedOutput() if err != nil { panic(err) } if strings.Contains(string(out), needle) { ch <- struct{}{} return } time.Sleep(1 * time.Second) } }() return ch } func startPostgres() error { cmd := exec.Command( "docker", "run", "--rm", "-p", postgresPort+":"+defaultPort, "-e", "POSTGRES_PASSWORD="+postgresPass, "-e", "POSTGRES_USER="+postgresUser, "-d", "postgres", ) fmt.Println(cmd.String()) out, err := cmd.Output() if err != nil { return err } postgresDockerHash = string(bytes.TrimSpace(out)) select { case <-dockerLogLine(postgresDockerHash, "PostgreSQL init process complete; ready for start up."): return nil case <-time.After(30 * time.Second): stopPostgres() return errors.New("timeout waiting for postgres database to be ready") } } func stopPostgres() { err := exec.Command("docker", "kill", postgresDockerHash).Run() if err != nil { fmt.Println("could not stop postgres container:", err) } }