Skip to content

Commit d3aba96

Browse files
Copilotintel352
andauthored
Address all 9 review thread comments: fix pr_merge method, release_upload templates, schema accuracy, token required, type corrections, runtime sync tests
Agent-Logs-Url: https://github.com/GoCodeAlone/workflow-plugin-github/sessions/e1f18cab-168a-453e-a97d-dd290c4cfcae Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
1 parent c6e8d22 commit d3aba96

5 files changed

Lines changed: 164 additions & 46 deletions

File tree

internal/schemas.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ func (p *githubPlugin) ModuleSchemas() []sdk.ModuleSchemaData {
1616
Category: "github",
1717
Description: "Receives GitHub webhook events via HTTP, verifies HMAC-SHA256 signatures, and publishes normalised GitEvent messages to a configurable topic.",
1818
ConfigFields: []sdk.ConfigField{
19-
{
20-
Name: "provider",
21-
Type: "string",
22-
Description: "Webhook provider identifier",
23-
DefaultValue: "github",
24-
Required: false,
25-
},
2619
{
2720
Name: "secret",
2821
Type: "string",
@@ -52,7 +45,7 @@ func (p *githubPlugin) ModuleSchemas() []sdk.ModuleSchemaData {
5245
{Name: "author", Type: "string", Description: "Event author username"},
5346
{Name: "message", Type: "string", Description: "Commit message or PR title"},
5447
{Name: "url", Type: "string", Description: "URL to the commit or PR"},
55-
{Name: "raw_payload", Type: "string", Description: "Raw JSON webhook payload"},
48+
{Name: "raw_payload", Type: "object", Description: "Raw JSON webhook payload"},
5649
{Name: "timestamp", Type: "string", Description: "Event timestamp in RFC3339 format"},
5750
},
5851
},

internal/schemas_test.go

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,54 @@ package internal
33
import (
44
"encoding/json"
55
"os"
6+
"sort"
67
"testing"
78

89
"github.com/GoCodeAlone/workflow/plugin"
910
)
1011

1112
// TestModuleSchemas verifies that the plugin's SchemaProvider returns schema
12-
// descriptors for both advertised module types.
13+
// descriptors for both advertised module types and stays in sync with
14+
// ModuleTypes() and plugin.json moduleTypes.
1315
func TestModuleSchemas(t *testing.T) {
1416
p := &githubPlugin{}
1517
schemas := p.ModuleSchemas()
18+
runtimeTypes := p.ModuleTypes()
1619

1720
if len(schemas) != 2 {
1821
t.Fatalf("expected 2 module schemas, got %d", len(schemas))
1922
}
2023

21-
byType := make(map[string]int, len(schemas))
24+
// Every schema type must appear in the runtime ModuleTypes() list.
25+
runtimeSet := make(map[string]bool, len(runtimeTypes))
26+
for _, mt := range runtimeTypes {
27+
runtimeSet[mt] = true
28+
}
29+
for _, s := range schemas {
30+
if !runtimeSet[s.Type] {
31+
t.Errorf("schema type %q is not in githubPlugin.ModuleTypes()", s.Type)
32+
}
33+
}
34+
35+
// Every runtime module type must have a schema entry.
36+
schemaSet := make(map[string]int, len(schemas))
2237
for i, s := range schemas {
23-
byType[s.Type] = i
38+
schemaSet[s.Type] = i
39+
}
40+
for _, mt := range runtimeTypes {
41+
if _, ok := schemaSet[mt]; !ok {
42+
t.Errorf("ModuleTypes() returns %q but no corresponding ModuleSchema exists", mt)
43+
}
2444
}
2545

2646
for _, wantType := range []string{"git.webhook", "github.app"} {
27-
if _, ok := byType[wantType]; !ok {
47+
if _, ok := schemaSet[wantType]; !ok {
2848
t.Errorf("missing module schema for type %q", wantType)
2949
}
3050
}
3151

32-
// git.webhook should have at least the four documented config fields.
33-
webhookIdx, ok := byType["git.webhook"]
52+
// git.webhook should have the core documented config fields.
53+
webhookIdx, ok := schemaSet["git.webhook"]
3454
if !ok {
3555
t.Fatalf("git.webhook schema not found")
3656
}
@@ -41,15 +61,27 @@ func TestModuleSchemas(t *testing.T) {
4161
if webhook.Description == "" {
4262
t.Error("git.webhook schema: Description must not be empty")
4363
}
44-
if len(webhook.ConfigFields) < 4 {
45-
t.Errorf("git.webhook schema: expected at least 4 config fields, got %d", len(webhook.ConfigFields))
64+
webhookFieldNames := make(map[string]bool, len(webhook.ConfigFields))
65+
for _, f := range webhook.ConfigFields {
66+
webhookFieldNames[f.Name] = true
67+
}
68+
for _, want := range []string{"secret", "events", "topic"} {
69+
if !webhookFieldNames[want] {
70+
t.Errorf("git.webhook schema: required config field %q is missing", want)
71+
}
4672
}
4773
if len(webhook.Outputs) == 0 {
4874
t.Error("git.webhook schema: expected at least one output")
4975
}
76+
// raw_payload must be declared as an object (json.RawMessage), not string.
77+
for _, f := range webhook.Outputs {
78+
if f.Name == "raw_payload" && f.Type != "object" {
79+
t.Errorf("git.webhook schema: raw_payload output type should be %q, got %q", "object", f.Type)
80+
}
81+
}
5082

5183
// github.app should declare the three required config fields.
52-
appIdx, ok := byType["github.app"]
84+
appIdx, ok := schemaSet["github.app"]
5385
if !ok {
5486
t.Fatalf("github.app schema not found")
5587
}
@@ -71,10 +103,37 @@ func TestModuleSchemas(t *testing.T) {
71103
t.Errorf("github.app schema: field %q should be marked required", want)
72104
}
73105
}
106+
107+
// Cross-check against plugin.json moduleTypes.
108+
data, err := os.ReadFile("../plugin.json")
109+
if err != nil {
110+
t.Fatalf("plugin.json not found: %v", err)
111+
}
112+
var manifest struct {
113+
ModuleTypes []string `json:"moduleTypes"`
114+
}
115+
if err := json.Unmarshal(data, &manifest); err != nil {
116+
t.Fatalf("parse plugin.json: %v", err)
117+
}
118+
jsonModuleSet := make(map[string]bool, len(manifest.ModuleTypes))
119+
for _, mt := range manifest.ModuleTypes {
120+
jsonModuleSet[mt] = true
121+
}
122+
for _, mt := range runtimeTypes {
123+
if !jsonModuleSet[mt] {
124+
t.Errorf("githubPlugin.ModuleTypes() returns %q but plugin.json moduleTypes does not include it", mt)
125+
}
126+
}
127+
for _, mt := range manifest.ModuleTypes {
128+
if !runtimeSet[mt] {
129+
t.Errorf("plugin.json moduleTypes includes %q but githubPlugin.ModuleTypes() does not", mt)
130+
}
131+
}
74132
}
75133

76134
// TestPluginStepSchemasJSON verifies that plugin.json can be parsed and that
77-
// it declares a stepSchemas entry for every step type the plugin advertises.
135+
// it declares a stepSchemas entry for every step type the plugin advertises,
136+
// and that both are in sync with the runtime StepTypes() list.
78137
func TestPluginStepSchemasJSON(t *testing.T) {
79138
// Locate plugin.json relative to the repository root (one level up from internal/).
80139
data, err := os.ReadFile("../plugin.json")
@@ -111,6 +170,40 @@ func TestPluginStepSchemasJSON(t *testing.T) {
111170
t.Errorf("plugin.json: stepSchemas count (%d) does not match stepTypes count (%d)",
112171
len(manifest.StepSchemas), len(manifest.StepTypes))
113172
}
173+
174+
// Cross-check JSON manifest against the runtime StepTypes() list.
175+
p := &githubPlugin{}
176+
runtimeTypes := p.StepTypes()
177+
178+
runtimeSet := make(map[string]bool, len(runtimeTypes))
179+
for _, st := range runtimeTypes {
180+
runtimeSet[st] = true
181+
}
182+
jsonTypeSet := make(map[string]bool, len(manifest.StepTypes))
183+
for _, st := range manifest.StepTypes {
184+
jsonTypeSet[st] = true
185+
}
186+
187+
for _, rt := range runtimeTypes {
188+
if !jsonTypeSet[rt] {
189+
t.Errorf("githubPlugin.StepTypes() returns %q but plugin.json stepTypes does not include it", rt)
190+
}
191+
}
192+
for _, jt := range manifest.StepTypes {
193+
if !runtimeSet[jt] {
194+
t.Errorf("plugin.json stepTypes includes %q but githubPlugin.StepTypes() does not return it", jt)
195+
}
196+
}
197+
198+
// Verify both lists have the same length (no duplicates or gaps).
199+
sortedRuntime := append([]string(nil), runtimeTypes...)
200+
sortedJSON := append([]string(nil), manifest.StepTypes...)
201+
sort.Strings(sortedRuntime)
202+
sort.Strings(sortedJSON)
203+
if len(sortedRuntime) != len(sortedJSON) {
204+
t.Errorf("runtime StepTypes() count (%d) != plugin.json stepTypes count (%d)",
205+
len(sortedRuntime), len(sortedJSON))
206+
}
114207
}
115208

116209
// TestPluginManifestEngineValidation verifies that plugin.json is parseable as a

internal/step_pr_merge.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"os"
77

8+
"github.com/google/go-github/v69/github"
9+
810
sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk"
911
)
1012

@@ -81,8 +83,9 @@ func (s *prMergeStep) Execute(
8183
commitTitle := resolveField(s.config.CommitTitle, triggerData, stepOutputs, current)
8284

8385
client := NewSDKClient(token)
86+
method := resolveField(s.config.Method, triggerData, stepOutputs, current)
8487
result, _, err := client.GH.PullRequests.Merge(ctx, owner, repo, s.config.PRNumber,
85-
commitTitle, nil)
88+
commitTitle, &github.PullRequestOptions{MergeMethod: method})
8689
if err != nil {
8790
return errorResult(fmt.Sprintf("merge PR: %v", err)), nil
8891
}

internal/step_release_upload.go

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"strconv"
8+
"strings"
79

810
"github.com/google/go-github/v69/github"
911

@@ -27,12 +29,13 @@ type releaseUploadStep struct {
2729
}
2830

2931
type releaseUploadConfig struct {
30-
Owner string `yaml:"owner"`
31-
Repo string `yaml:"repo"`
32-
ReleaseID int64 `yaml:"release_id"`
33-
File string `yaml:"file"`
34-
Name string `yaml:"name"`
35-
Token string `yaml:"token"`
32+
Owner string `yaml:"owner"`
33+
Repo string `yaml:"repo"`
34+
ReleaseID int64 `yaml:"release_id"`
35+
ReleaseIDRaw string `yaml:"-"` // raw string for dynamic {{.field}} resolution
36+
File string `yaml:"file"`
37+
Name string `yaml:"name"`
38+
Token string `yaml:"token"`
3639
}
3740

3841
func newReleaseUploadStep(name string, raw map[string]any) (*releaseUploadStep, error) {
@@ -52,8 +55,20 @@ func newReleaseUploadStep(name string, raw map[string]any) (*releaseUploadStep,
5255
cfg.ReleaseID = v
5356
case float64:
5457
cfg.ReleaseID = int64(v)
58+
case string:
59+
if v != "" {
60+
if strings.Contains(v, "{{") && strings.Contains(v, "}}") {
61+
cfg.ReleaseIDRaw = v
62+
} else {
63+
n, err := strconv.ParseInt(v, 10, 64)
64+
if err != nil {
65+
return nil, fmt.Errorf("step.gh_release_upload %q: config.release_id is not a valid integer: %w", name, err)
66+
}
67+
cfg.ReleaseID = n
68+
}
69+
}
5570
}
56-
if cfg.ReleaseID == 0 {
71+
if cfg.ReleaseID == 0 && cfg.ReleaseIDRaw == "" {
5772
return nil, fmt.Errorf("step.gh_release_upload %q: config.release_id is required", name)
5873
}
5974
cfg.File, _ = raw["file"].(string)
@@ -86,6 +101,20 @@ func (s *releaseUploadStep) Execute(
86101
assetName = filePath
87102
}
88103

104+
// Resolve release_id — may be a static int or a dynamic template reference.
105+
releaseID := s.config.ReleaseID
106+
if s.config.ReleaseIDRaw != "" {
107+
resolved := resolveField(s.config.ReleaseIDRaw, triggerData, stepOutputs, current)
108+
n, err := strconv.ParseInt(resolved, 10, 64)
109+
if err != nil {
110+
return errorResult(fmt.Sprintf("release_id resolved to non-integer value %q: %v", resolved, err)), nil
111+
}
112+
releaseID = n
113+
}
114+
if releaseID == 0 {
115+
return errorResult("release_id resolved to zero — check pipeline context"), nil
116+
}
117+
89118
f, err := os.Open(filePath) //nolint:gosec // G304: path from step config, trusted
90119
if err != nil {
91120
return errorResult(fmt.Sprintf("open file %q: %v", filePath, err)), nil
@@ -98,7 +127,7 @@ func (s *releaseUploadStep) Execute(
98127
}
99128

100129
client := NewSDKClient(token)
101-
asset, _, err := client.GH.Repositories.UploadReleaseAsset(ctx, owner, repo, s.config.ReleaseID,
130+
asset, _, err := client.GH.Repositories.UploadReleaseAsset(ctx, owner, repo, releaseID,
102131
&github.UploadOptions{Name: assetName},
103132
f)
104133
if err != nil {

0 commit comments

Comments
 (0)