@@ -11,10 +11,20 @@ import (
1111 "github.com/spf13/cobra"
1212)
1313
14- // loadSpecPaths reads every GET/POST operation from the openapi spec shipped in
15- // the linked go-flashduty module — the same spec cligen generates against —
16- // returning operationId -> path.
17- func loadSpecPaths (t * testing.T ) map [string ]string {
14+ // specOpMeta is the slice of an operation the coverage tests reason about.
15+ type specOpMeta struct {
16+ id string
17+ path string
18+ streaming bool // 200 body is not application/json (e.g. application/x-ndjson)
19+ }
20+
21+ // loadSpecOps reads every public GET/POST operation from the openapi spec
22+ // shipped in the linked go-flashduty module — the same spec cligen generates
23+ // against — recording each op's id, path, and whether its 200 response is a
24+ // non-JSON streaming body. Streaming ops are served by curated commands (the
25+ // generated typed-response template cannot model an io.ReadCloser), so the
26+ // generator-coverage check excludes them.
27+ func loadSpecOps (t * testing.T ) []specOpMeta {
1828 t .Helper ()
1929 out , err := exec .Command ("go" , "list" , "-m" , "-f" , "{{.Dir}}" , "github.com/flashcatcloud/go-flashduty" ).Output ()
2030 if err != nil {
@@ -29,12 +39,15 @@ func loadSpecPaths(t *testing.T) map[string]string {
2939 Paths map [string ]map [string ]struct {
3040 OperationID string `json:"operationId"`
3141 Tags []string `json:"tags"`
42+ Responses map [string ]struct {
43+ Content map [string ]json.RawMessage `json:"content"`
44+ } `json:"responses"`
3245 } `json:"paths"`
3346 }
3447 if err := json .Unmarshal (data , & spec ); err != nil {
3548 t .Fatalf ("parse spec: %v" , err )
3649 }
37- ids := map [ string ] string {}
50+ var ops [] specOpMeta
3851 for path , methods := range spec .Paths {
3952 for verb , op := range methods {
4053 v := strings .ToUpper (verb )
@@ -44,9 +57,25 @@ func loadSpecPaths(t *testing.T) map[string]string {
4457 if op .OperationID == "" || len (op .Tags ) == 0 {
4558 continue
4659 }
47- ids [op .OperationID ] = path
60+ streaming := false
61+ if resp , ok := op .Responses ["200" ]; ok && len (resp .Content ) > 0 {
62+ if _ , hasJSON := resp .Content ["application/json" ]; ! hasJSON {
63+ streaming = true
64+ }
65+ }
66+ ops = append (ops , specOpMeta {id : op .OperationID , path : path , streaming : streaming })
4867 }
4968 }
69+ return ops
70+ }
71+
72+ // loadSpecPaths returns operationId -> path for every public GET/POST operation.
73+ func loadSpecPaths (t * testing.T ) map [string ]string {
74+ t .Helper ()
75+ ids := map [string ]string {}
76+ for _ , op := range loadSpecOps (t ) {
77+ ids [op .id ] = op .path
78+ }
5079 return ids
5180}
5281
@@ -110,20 +139,38 @@ func TestEveryOperationHasPathCommand(t *testing.T) {
110139}
111140
112141// TestGeneratorTargetsFullSpec asserts the generator emitted a command for every
113- // spec operation (no gaps, no phantom manifest entries from a stale run).
142+ // non-streaming spec operation (no gaps, no phantom manifest entries from a
143+ // stale run). Streaming ops (200 body is not application/json) are deliberately
144+ // excluded from generation — they cannot be modeled by the typed-response
145+ // template and are served by curated commands instead — so the manifest must NOT
146+ // contain them and they are not required to be generated.
114147func TestGeneratorTargetsFullSpec (t * testing.T ) {
115- specPaths := loadSpecPaths (t )
148+ ops := loadSpecOps (t )
149+ streaming := map [string ]bool {}
150+ wantGenerated := map [string ]bool {}
151+ for _ , op := range ops {
152+ if op .streaming {
153+ streaming [op .id ] = true
154+ continue
155+ }
156+ wantGenerated [op .id ] = true
157+ }
158+
116159 gen := map [string ]bool {}
117160 for _ , id := range generatedOpIDs {
118161 gen [id ] = true
119- if _ , ok := specPaths [id ]; ! ok {
162+ if streaming [id ] {
163+ t .Errorf ("manifest op %q is streaming and must not be generated (curated only)" , id )
164+ }
165+ if ! wantGenerated [id ] && ! streaming [id ] {
120166 t .Errorf ("manifest op %q is not in the current spec (regenerate cligen)" , id )
121167 }
122168 }
123- for id := range specPaths {
169+ for id := range wantGenerated {
124170 if ! gen [id ] {
125171 t .Errorf ("op %q has no generated command (regenerate cligen)" , id )
126172 }
127173 }
128- t .Logf ("generator targets %d/%d spec operations" , len (gen ), len (specPaths ))
174+ t .Logf ("generator targets %d/%d non-streaming spec operations (%d streaming, curated)" ,
175+ len (gen ), len (wantGenerated ), len (streaming ))
129176}
0 commit comments