@@ -2,42 +2,218 @@ package templates
22
33import (
44 "bytes"
5+ "context"
6+ "encoding/json"
7+ "errors"
8+ "fmt"
9+ "io"
10+ "path/filepath"
11+ "strings"
512 "testing"
13+ "time"
614
715 "github.com/spf13/cobra"
16+
17+ templatestore "github.com/endalk200/better-webhook/apps/webhook-cli/internal/adapters/storage/template"
18+ apptemplates "github.com/endalk200/better-webhook/apps/webhook-cli/internal/app/templates"
19+ domain "github.com/endalk200/better-webhook/apps/webhook-cli/internal/domain/template"
20+ "github.com/endalk200/better-webhook/apps/webhook-cli/internal/platform/runtime"
21+ platformtime "github.com/endalk200/better-webhook/apps/webhook-cli/internal/platform/time"
822)
923
10- func TestPromptTemplateCleanConfirmHandlesEOFWithoutTrailingNewline (t * testing.T ) {
11- command := & cobra.Command {Use : "test" }
12- command .SetIn (bytes .NewBufferString ("y" ))
13- command .SetOut (& bytes.Buffer {})
24+ func TestCleanCommandRejectsUnexpectedArgs (t * testing.T ) {
25+ cmd := newCleanCommand (Dependencies {})
26+ if err := cmd .Args (cmd , []string {"unexpected" }); err == nil {
27+ t .Fatalf ("expected clean command to reject positional args" )
28+ }
29+ }
30+
31+ type fakeTemplatePrompter struct {
32+ confirmed bool
33+ err error
34+ called int
35+ }
36+
37+ func (p * fakeTemplatePrompter ) Confirm (_ string , _ io.Reader , _ io.Writer ) (bool , error ) {
38+ p .called ++
39+ if p .err != nil {
40+ return false , p .err
41+ }
42+ return p .confirmed , nil
43+ }
44+
45+ func TestCleanCommandCancelledByPromptKeepsTemplates (t * testing.T ) {
46+ templatesDir := t .TempDir ()
47+ seedLocalTemplateForCleanTest (t , templatesDir , "github-push" )
48+ prompter := & fakeTemplatePrompter {confirmed : false }
49+
50+ cmd := newCleanCommand (Dependencies {
51+ ServiceFactory : testTemplateServiceFactory (t ),
52+ Prompter : prompter ,
53+ })
54+ var output bytes.Buffer
55+ cmd .SetOut (& output )
56+ cmd .SetErr (& output )
57+ cmd .SetIn (strings .NewReader ("unused\n " ))
58+ cmd .SetArgs ([]string {"--templates-dir" , templatesDir })
59+ initializeTemplatesRuntimeConfig (t , cmd , templatesDir )
1460
15- confirmed , err := promptTemplateCleanConfirm (command , "Delete? " )
61+ if err := cmd .Execute (); err != nil {
62+ t .Fatalf ("execute clean command: %v" , err )
63+ }
64+ if prompter .called != 1 {
65+ t .Fatalf ("expected prompter to be called once, got %d" , prompter .called )
66+ }
67+ if ! strings .Contains (output .String (), "Cancelled." ) {
68+ t .Fatalf ("expected cancellation output, got %q" , output .String ())
69+ }
70+
71+ store , err := templatestore .NewStore (templatesDir )
1672 if err != nil {
17- t .Fatalf ("prompt confirm returned error : %v" , err )
73+ t .Fatalf ("create local template store : %v" , err )
1874 }
19- if ! confirmed {
20- t .Fatalf ("expected EOF input without newline to be treated as confirmation" )
75+ items , err := store .List (context .Background ())
76+ if err != nil {
77+ t .Fatalf ("list local templates: %v" , err )
78+ }
79+ if len (items ) != 1 {
80+ t .Fatalf ("expected template to remain after cancellation, got %d" , len (items ))
2181 }
2282}
2383
24- func TestPromptTemplateCleanConfirmTreatsEmptyEOFAsCancel (t * testing.T ) {
25- command := & cobra.Command {Use : "test" }
26- command .SetIn (bytes .NewBuffer (nil ))
27- command .SetOut (& bytes.Buffer {})
84+ func TestCleanCommandForceSkipsPromptAndDeletesTemplates (t * testing.T ) {
85+ templatesDir := t .TempDir ()
86+ seedLocalTemplateForCleanTest (t , templatesDir , "github-push" )
87+ prompter := & fakeTemplatePrompter {err : errors .New ("prompter should not be called" )}
88+
89+ cmd := newCleanCommand (Dependencies {
90+ ServiceFactory : testTemplateServiceFactory (t ),
91+ Prompter : prompter ,
92+ })
93+ var output bytes.Buffer
94+ cmd .SetOut (& output )
95+ cmd .SetErr (& output )
96+ cmd .SetArgs ([]string {"--templates-dir" , templatesDir , "--force" })
97+ initializeTemplatesRuntimeConfig (t , cmd , templatesDir )
2898
29- confirmed , err := promptTemplateCleanConfirm (command , "Delete? " )
99+ if err := cmd .Execute (); err != nil {
100+ t .Fatalf ("execute clean command: %v" , err )
101+ }
102+ if prompter .called != 0 {
103+ t .Fatalf ("expected --force to skip prompt, got %d calls" , prompter .called )
104+ }
105+ if ! strings .Contains (output .String (), "Removed 1 template(s)" ) {
106+ t .Fatalf ("expected clean success output, got %q" , output .String ())
107+ }
108+
109+ store , err := templatestore .NewStore (templatesDir )
30110 if err != nil {
31- t .Fatalf ("prompt confirm returned error : %v" , err )
111+ t .Fatalf ("create local template store : %v" , err )
32112 }
33- if confirmed {
34- t .Fatalf ("expected empty EOF input to be treated as cancel" )
113+ items , err := store .List (context .Background ())
114+ if err != nil {
115+ t .Fatalf ("list local templates: %v" , err )
116+ }
117+ if len (items ) != 0 {
118+ t .Fatalf ("expected templates to be deleted, got %d" , len (items ))
35119 }
36120}
37121
38- func TestCleanCommandRejectsUnexpectedArgs (t * testing.T ) {
39- cmd := newCleanCommand (Dependencies {})
40- if err := cmd .Args (cmd , []string {"unexpected" }); err == nil {
41- t .Fatalf ("expected clean command to reject positional args" )
122+ func TestCleanCommandReturnsPromptError (t * testing.T ) {
123+ templatesDir := t .TempDir ()
124+ seedLocalTemplateForCleanTest (t , templatesDir , "github-push" )
125+ prompter := & fakeTemplatePrompter {err : errors .New ("prompt failed" )}
126+
127+ cmd := newCleanCommand (Dependencies {
128+ ServiceFactory : testTemplateServiceFactory (t ),
129+ Prompter : prompter ,
130+ })
131+ cmd .SetOut (& bytes.Buffer {})
132+ cmd .SetErr (& bytes.Buffer {})
133+ cmd .SetArgs ([]string {"--templates-dir" , templatesDir })
134+ initializeTemplatesRuntimeConfig (t , cmd , templatesDir )
135+
136+ err := cmd .Execute ()
137+ if err == nil {
138+ t .Fatalf ("expected clean command to return prompt error" )
139+ }
140+ if ! strings .Contains (err .Error (), "prompt failed" ) {
141+ t .Fatalf ("expected prompt error to be returned, got %v" , err )
142+ }
143+ }
144+
145+ type noOpRemoteTemplateSource struct {}
146+
147+ func (noOpRemoteTemplateSource ) FetchIndex (context.Context ) (domain.TemplatesIndex , error ) {
148+ return domain.TemplatesIndex {}, errors .New ("not implemented" )
149+ }
150+
151+ func (noOpRemoteTemplateSource ) FetchTemplate (context.Context , string ) (domain.WebhookTemplate , error ) {
152+ return domain.WebhookTemplate {}, errors .New ("not implemented" )
153+ }
154+
155+ func testTemplateServiceFactory (t * testing.T ) ServiceFactory {
156+ t .Helper ()
157+ return func (templatesDir string ) (* apptemplates.Service , error ) {
158+ localStore , err := templatestore .NewStore (templatesDir )
159+ if err != nil {
160+ return nil , err
161+ }
162+ cacheStore , err := templatestore .NewCache (filepath .Join (templatesDir , ".index-cache.json" ))
163+ if err != nil {
164+ return nil , err
165+ }
166+ return apptemplates .NewService (
167+ localStore ,
168+ noOpRemoteTemplateSource {},
169+ cacheStore ,
170+ platformtime.SystemClock {},
171+ ), nil
172+ }
173+ }
174+
175+ func seedLocalTemplateForCleanTest (t * testing.T , templatesDir string , id string ) {
176+ t .Helper ()
177+ store , err := templatestore .NewStore (templatesDir )
178+ if err != nil {
179+ t .Fatalf ("create local template store: %v" , err )
180+ }
181+
182+ downloadedAt := time .Date (2026 , time .February , 24 , 12 , 0 , 0 , 0 , time .UTC ).Format (time .RFC3339Nano )
183+ templateFile := fmt .Sprintf ("github/%s.jsonc" , id )
184+ if _ , err := store .Save (context .Background (), domain.TemplateMetadata {
185+ ID : id ,
186+ Name : id ,
187+ Provider : "github" ,
188+ Event : "push" ,
189+ File : templateFile ,
190+ }, domain.WebhookTemplate {
191+ Method : "POST" ,
192+ Provider : "github" ,
193+ Event : "push" ,
194+ Body : json .RawMessage (`{"ok":true}` ),
195+ }, downloadedAt ); err != nil {
196+ t .Fatalf ("seed local template: %v" , err )
197+ }
198+ }
199+
200+ type staticTemplateConfigLoader struct {
201+ config runtime.AppConfig
202+ }
203+
204+ func (l staticTemplateConfigLoader ) Load (_ string ) (runtime.AppConfig , error ) {
205+ return l .config , nil
206+ }
207+
208+ func initializeTemplatesRuntimeConfig (t * testing.T , cmd * cobra.Command , templatesDir string ) {
209+ t .Helper ()
210+ if err := runtime .InitializeConfig (cmd , staticTemplateConfigLoader {
211+ config : runtime.AppConfig {
212+ CapturesDir : t .TempDir (),
213+ TemplatesDir : templatesDir ,
214+ LogLevel : runtime .LogLevelInfo ,
215+ },
216+ }); err != nil {
217+ t .Fatalf ("initialize runtime config: %v" , err )
42218 }
43219}
0 commit comments