@@ -20,6 +20,7 @@ import (
2020 "os"
2121 "os/exec"
2222 "path/filepath"
23+ "strings"
2324 "testing"
2425
2526 "github.com/stretchr/testify/assert"
@@ -219,6 +220,58 @@ func TestInstallRefusesToClobberBackup(t *testing.T) {
219220 assert .Equal (t , original , string (content ))
220221}
221222
223+ // TestHookGoldenFiles compares the hook scripts Install writes against the
224+ // committed golden files in testdata/. The temporary hooks directory is
225+ // replaced by a placeholder so the files stay stable. Run with
226+ // UPDATE_GOLDEN=1 to regenerate them after an intentional change:
227+ //
228+ // UPDATE_GOLDEN=1 go test ./app/cli/internal/trace/hooks/ -run TestHookGoldenFiles
229+ func TestHookGoldenFiles (t * testing.T ) {
230+ const hooksDirPlaceholder = "<hooks-dir>"
231+
232+ testCases := []struct {
233+ hook string
234+ foreignHook bool
235+ golden string
236+ }{
237+ {hook : "commit-msg" , golden : "testdata/commit-msg.sh" },
238+ {hook : "post-commit" , golden : "testdata/post-commit.sh" },
239+ {hook : "post-rewrite" , golden : "testdata/post-rewrite.sh" },
240+ {hook : "pre-push" , golden : "testdata/pre-push.sh" },
241+ {hook : "pre-push" , foreignHook : true , golden : "testdata/pre-push-chained.sh" },
242+ {hook : "post-commit" , foreignHook : true , golden : "testdata/post-commit-chained.sh" },
243+ }
244+
245+ for _ , tc := range testCases {
246+ t .Run (filepath .Base (tc .golden ), func (t * testing.T ) {
247+ gitDir := t .TempDir ()
248+ hooksDir := filepath .Join (gitDir , "hooks" )
249+ require .NoError (t , os .MkdirAll (hooksDir , 0755 ))
250+ if tc .foreignHook {
251+ require .NoError (t , os .WriteFile (filepath .Join (hooksDir , tc .hook ), []byte ("#!/bin/sh\n " ), 0600 ))
252+ }
253+
254+ _ , err := Install (gitDir , false )
255+ require .NoError (t , err )
256+
257+ content , err := os .ReadFile (filepath .Join (hooksDir , tc .hook ))
258+ require .NoError (t , err )
259+ generated := strings .ReplaceAll (string (content ), hooksDir , hooksDirPlaceholder )
260+
261+ if os .Getenv ("UPDATE_GOLDEN" ) == "1" {
262+ //nolint:gosec // golden paths are fixed testdata paths from the table above
263+ require .NoError (t , os .WriteFile (tc .golden , []byte (generated ), 0600 ))
264+ return
265+ }
266+
267+ expected , err := os .ReadFile (tc .golden )
268+ require .NoError (t , err , "golden file missing; run UPDATE_GOLDEN=1 to generate" )
269+ assert .Equal (t , string (expected ), generated ,
270+ "generated hook does not match golden file %s; run UPDATE_GOLDEN=1 to update" , tc .golden )
271+ })
272+ }
273+ }
274+
222275// TestHookScriptsExitStatus runs each generated hook against a fake chainloop
223276// binary, or none at all (as in GUI git clients). Only pre-push propagates a
224277// chainloop failure, so requireTrace can block the push; every other hook must
0 commit comments