Skip to content

Commit 1df7e69

Browse files
Copilotintel352
andauthored
Address remaining review comments: fix type/description issues, re-add provider, fix CI informational step, add field-level contract tests
Agent-Logs-Url: https://github.com/GoCodeAlone/workflow-plugin-github/sessions/c9cbf541-38c2-4769-b1b9-80f1ca0007ac Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
1 parent 5945a1b commit 1df7e69

4 files changed

Lines changed: 133 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ jobs:
4141
run: |
4242
# wfctl validates registry-format manifests; strict contract schema coverage is enforced
4343
# by the Go tests above. This step runs informational validation and logs the result.
44-
go run github.com/GoCodeAlone/workflow/cmd/wfctl@v0.3.56 plugin validate --file plugin.json 2>&1; wfctl_exit=$?
44+
set +e
45+
go run github.com/GoCodeAlone/workflow/cmd/wfctl@v0.3.56 plugin validate --file plugin.json 2>&1
46+
wfctl_exit=$?
47+
set -e
4548
echo "wfctl validation exit code: ${wfctl_exit}"
4649
env:
4750
GOPRIVATE: github.com/GoCodeAlone/*

internal/schemas.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ 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. Accepted for backward compatibility; the module always publishes events with provider 'github'.",
23+
DefaultValue: "github",
24+
Required: false,
25+
},
1926
{
2027
Name: "secret",
2128
Type: "string",

internal/schemas_test.go

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TestModuleSchemas(t *testing.T) {
6565
for _, f := range webhook.ConfigFields {
6666
webhookFieldNames[f.Name] = true
6767
}
68-
for _, want := range []string{"secret", "events", "topic"} {
68+
for _, want := range []string{"provider", "secret", "events", "topic"} {
6969
if !webhookFieldNames[want] {
7070
t.Errorf("git.webhook schema: required config field %q is missing", want)
7171
}
@@ -206,6 +206,122 @@ func TestPluginStepSchemasJSON(t *testing.T) {
206206
}
207207
}
208208

209+
// TestStepSchemaFieldContracts verifies critical field-level properties across
210+
// step schemas in plugin.json: token required/sensitive, template-capable fields
211+
// typed as string, and key required flags.
212+
func TestStepSchemaFieldContracts(t *testing.T) {
213+
data, err := os.ReadFile("../plugin.json")
214+
if err != nil {
215+
t.Fatalf("plugin.json not found: %v", err)
216+
}
217+
218+
type configField struct {
219+
Key string `json:"key"`
220+
Type string `json:"type"`
221+
Required bool `json:"required"`
222+
Sensitive bool `json:"sensitive"`
223+
DefaultValue any `json:"defaultValue"`
224+
}
225+
type stepSchema struct {
226+
Type string `json:"type"`
227+
ConfigFields []configField `json:"configFields"`
228+
}
229+
var manifest struct {
230+
StepSchemas []stepSchema `json:"stepSchemas"`
231+
}
232+
if err := json.Unmarshal(data, &manifest); err != nil {
233+
t.Fatalf("parse plugin.json: %v", err)
234+
}
235+
236+
// Index schemas by type for easy lookup.
237+
byType := make(map[string]stepSchema, len(manifest.StepSchemas))
238+
for _, s := range manifest.StepSchemas {
239+
byType[s.Type] = s
240+
}
241+
fieldsByKey := func(s stepSchema) map[string]configField {
242+
m := make(map[string]configField, len(s.ConfigFields))
243+
for _, f := range s.ConfigFields {
244+
m[f.Key] = f
245+
}
246+
return m
247+
}
248+
249+
// Every step schema with a token field must mark it required and sensitive.
250+
for _, s := range manifest.StepSchemas {
251+
fields := fieldsByKey(s)
252+
if tok, ok := fields["token"]; ok {
253+
if !tok.Required {
254+
t.Errorf("%s: token field must be required=true (step fails at runtime without it)", s.Type)
255+
}
256+
if !tok.Sensitive {
257+
t.Errorf("%s: token field must be sensitive=true", s.Type)
258+
}
259+
}
260+
}
261+
262+
// Fields that accept both numeric literals and template expressions must be
263+
// declared as string so schema-aware validators accept template syntax.
264+
templateCapableFields := map[string]string{
265+
"step.gh_action_status": "run_id",
266+
"step.gh_release_upload": "release_id",
267+
}
268+
for stepType, fieldKey := range templateCapableFields {
269+
s, ok := byType[stepType]
270+
if !ok {
271+
t.Errorf("%s: schema not found", stepType)
272+
continue
273+
}
274+
fields := fieldsByKey(s)
275+
f, ok := fields[fieldKey]
276+
if !ok {
277+
t.Errorf("%s: field %q not found in schema", stepType, fieldKey)
278+
continue
279+
}
280+
if f.Type != "string" {
281+
t.Errorf("%s: field %q type should be %q (supports template expressions), got %q",
282+
stepType, fieldKey, "string", f.Type)
283+
}
284+
if !f.Required {
285+
t.Errorf("%s: field %q should be required=true", stepType, fieldKey)
286+
}
287+
}
288+
289+
// Dynamic string fields that also accept template expressions must not be
290+
// declared as select (which would prevent template expressions).
291+
noSelectFields := map[string]string{
292+
"step.gh_pr_merge": "method",
293+
"step.gh_pr_review": "event",
294+
}
295+
for stepType, fieldKey := range noSelectFields {
296+
s, ok := byType[stepType]
297+
if !ok {
298+
t.Errorf("%s: schema not found", stepType)
299+
continue
300+
}
301+
fields := fieldsByKey(s)
302+
f, ok := fields[fieldKey]
303+
if !ok {
304+
t.Errorf("%s: field %q not found in schema", stepType, fieldKey)
305+
continue
306+
}
307+
if f.Type == "select" {
308+
t.Errorf("%s: field %q should not be type 'select' — resolveField supports templates so the type must be 'string'", stepType, fieldKey)
309+
}
310+
}
311+
312+
// gh_secret_set: repo must be required (no org-secret path exists).
313+
if s, ok := byType["step.gh_secret_set"]; ok {
314+
fields := fieldsByKey(s)
315+
if repo, ok := fields["repo"]; ok {
316+
if !repo.Required {
317+
t.Errorf("step.gh_secret_set: repo field must be required=true (only repo secrets are supported)")
318+
}
319+
} else {
320+
t.Errorf("step.gh_secret_set: repo field missing from schema")
321+
}
322+
}
323+
}
324+
209325
// TestPluginManifestEngineValidation verifies that plugin.json is parseable as a
210326
// workflow engine PluginManifest and that Validate() passes required-field checks.
211327
func TestPluginManifestEngineValidation(t *testing.T) {

plugin.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"configFields": [
5959
{"key": "owner", "type": "string", "description": "GitHub repository owner", "required": true},
6060
{"key": "repo", "type": "string", "description": "GitHub repository name", "required": true},
61-
{"key": "run_id", "type": "number", "description": "Workflow run ID (integer or template expression e.g. {{.steps.trigger.run_id}})", "required": true},
61+
{"key": "run_id", "type": "string", "description": "Workflow run ID (numeric literal or template expression e.g. {{.steps.trigger_step.run_id}})", "required": true},
6262
{"key": "token", "type": "string", "description": "GitHub personal access token", "required": true, "sensitive": true},
6363
{"key": "wait", "type": "boolean", "description": "Poll until the run reaches a terminal state", "defaultValue": false},
6464
{"key": "poll_interval", "type": "duration", "description": "Interval between status polls when wait=true", "defaultValue": "10s"},
@@ -74,7 +74,7 @@
7474
{
7575
"type": "step.gh_create_check",
7676
"plugin": "workflow-plugin-github",
77-
"description": "Creates or updates a GitHub Check Run (status check) on a specific commit.",
77+
"description": "Creates a GitHub Check Run (status check) on a specific commit.",
7878
"configFields": [
7979
{"key": "owner", "type": "string", "description": "GitHub repository owner", "required": true},
8080
{"key": "repo", "type": "string", "description": "GitHub repository name", "required": true},
@@ -122,7 +122,7 @@
122122
{"key": "repo", "type": "string", "description": "GitHub repository name", "required": true},
123123
{"key": "pr_number", "type": "number", "description": "Pull request number to merge", "required": true},
124124
{"key": "commit_title", "type": "string", "description": "Merge commit title"},
125-
{"key": "method", "type": "select", "description": "Merge method", "options": ["merge", "squash", "rebase"], "defaultValue": "merge"},
125+
{"key": "method", "type": "string", "description": "Merge method: merge, squash, or rebase (also accepts template expressions)", "defaultValue": "merge"},
126126
{"key": "token", "type": "string", "description": "GitHub personal access token", "required": true, "sensitive": true}
127127
],
128128
"outputs": [
@@ -155,7 +155,7 @@
155155
{"key": "owner", "type": "string", "description": "GitHub repository owner", "required": true},
156156
{"key": "repo", "type": "string", "description": "GitHub repository name", "required": true},
157157
{"key": "pr_number", "type": "number", "description": "Pull request number", "required": true},
158-
{"key": "event", "type": "select", "description": "Review event type", "options": ["APPROVE", "REQUEST_CHANGES", "COMMENT"], "defaultValue": "COMMENT"},
158+
{"key": "event", "type": "string", "description": "Review event type: APPROVE, REQUEST_CHANGES, or COMMENT (also accepts template expressions)", "defaultValue": "COMMENT"},
159159
{"key": "body", "type": "string", "description": "Review body text"},
160160
{"key": "token", "type": "string", "description": "GitHub personal access token", "required": true, "sensitive": true}
161161
],
@@ -249,7 +249,7 @@
249249
"configFields": [
250250
{"key": "owner", "type": "string", "description": "GitHub repository owner", "required": true},
251251
{"key": "repo", "type": "string", "description": "GitHub repository name", "required": true},
252-
{"key": "release_id", "type": "number", "description": "Release ID (integer or template expression e.g. {{.steps.create_release.release_id}})", "required": true},
252+
{"key": "release_id", "type": "string", "description": "Release ID (numeric literal or template expression e.g. {{.steps.create_release.release_id}})", "required": true},
253253
{"key": "file", "type": "filepath", "description": "Local path to the file to upload", "required": true},
254254
{"key": "name", "type": "string", "description": "Display name for the release asset"},
255255
{"key": "token", "type": "string", "description": "GitHub personal access token", "required": true, "sensitive": true}

0 commit comments

Comments
 (0)