tenseleyflow/shithub / 6c9b30c

Browse files

actions/dispatch: unit tests for shared validation

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
6c9b30ca8be15f23a89ff054f1a07a1561745147
Parents
69ca758
Tree
3d3af23

1 changed file

StatusFile+-
A internal/actions/dispatch/dispatch_test.go 159 0
internal/actions/dispatch/dispatch_test.goadded
@@ -0,0 +1,159 @@
1
+// SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+package dispatch_test
4
+
5
+import (
6
+	"errors"
7
+	"net/url"
8
+	"testing"
9
+
10
+	"github.com/tenseleyFlow/shithub/internal/actions/dispatch"
11
+	"github.com/tenseleyFlow/shithub/internal/actions/workflow"
12
+)
13
+
14
+func TestNormalizeFilePath_AcceptsBasenameAndFullPath(t *testing.T) {
15
+	cases := []struct {
16
+		in   string
17
+		want string
18
+	}{
19
+		{"ci.yml", ".shithub/workflows/ci.yml"},
20
+		{".shithub/workflows/ci.yml", ".shithub/workflows/ci.yml"},
21
+		{"  ci.yaml  ", ".shithub/workflows/ci.yaml"},
22
+	}
23
+	for _, tc := range cases {
24
+		got, err := dispatch.NormalizeFilePath(tc.in)
25
+		if err != nil {
26
+			t.Errorf("NormalizeFilePath(%q): %v", tc.in, err)
27
+			continue
28
+		}
29
+		if got != tc.want {
30
+			t.Errorf("NormalizeFilePath(%q) = %q, want %q", tc.in, got, tc.want)
31
+		}
32
+	}
33
+}
34
+
35
+func TestNormalizeFilePath_RejectsBadInputs(t *testing.T) {
36
+	bad := []string{
37
+		"",
38
+		"../passwd",
39
+		".shithub/workflows/../escape.yml",
40
+		".shithub/workflows/sub/dir.yml",
41
+		".shithub/workflows/ci.txt",
42
+		"foo.txt",
43
+	}
44
+	for _, in := range bad {
45
+		if _, err := dispatch.NormalizeFilePath(in); !errors.Is(err, dispatch.ErrInvalidWorkflowName) {
46
+			t.Errorf("NormalizeFilePath(%q): got err %v, want ErrInvalidWorkflowName", in, err)
47
+		}
48
+	}
49
+}
50
+
51
+func TestNormalizeInputs_HappyPath(t *testing.T) {
52
+	specs := []workflow.DispatchInput{
53
+		{Name: "env", Type: "choice", Options: []string{"qa", "prod"}, Default: "qa"},
54
+		{Name: "debug", Type: "boolean"},
55
+		{Name: "ref", Type: "string", Required: true},
56
+	}
57
+	out, err := dispatch.NormalizeInputs(map[string]string{"env": "prod", "ref": "trunk"}, specs)
58
+	if err != nil {
59
+		t.Fatalf("NormalizeInputs: %v", err)
60
+	}
61
+	if out["env"] != "prod" {
62
+		t.Errorf("env: got %q", out["env"])
63
+	}
64
+	// Missing boolean → "false" default.
65
+	if out["debug"] != "false" {
66
+		t.Errorf("debug default: got %q", out["debug"])
67
+	}
68
+	if out["ref"] != "trunk" {
69
+		t.Errorf("ref: got %q", out["ref"])
70
+	}
71
+}
72
+
73
+func TestNormalizeInputs_RejectsUnknown(t *testing.T) {
74
+	specs := []workflow.DispatchInput{{Name: "env", Type: "string"}}
75
+	if _, err := dispatch.NormalizeInputs(map[string]string{"bogus": "x"}, specs); err == nil {
76
+		t.Fatal("unknown input accepted")
77
+	}
78
+}
79
+
80
+func TestNormalizeInputs_RejectsInvalidChoice(t *testing.T) {
81
+	specs := []workflow.DispatchInput{
82
+		{Name: "env", Type: "choice", Options: []string{"qa", "prod"}},
83
+	}
84
+	if _, err := dispatch.NormalizeInputs(map[string]string{"env": "stage"}, specs); err == nil {
85
+		t.Fatal("invalid choice accepted")
86
+	}
87
+}
88
+
89
+func TestNormalizeInputs_RejectsBadBoolean(t *testing.T) {
90
+	specs := []workflow.DispatchInput{{Name: "debug", Type: "boolean"}}
91
+	if _, err := dispatch.NormalizeInputs(map[string]string{"debug": "yes"}, specs); err == nil {
92
+		t.Fatal("non-bool accepted")
93
+	}
94
+}
95
+
96
+func TestNormalizeInputs_RequiredEnforced(t *testing.T) {
97
+	specs := []workflow.DispatchInput{{Name: "ref", Type: "string", Required: true}}
98
+	if _, err := dispatch.NormalizeInputs(nil, specs); err == nil {
99
+		t.Fatal("missing required input accepted")
100
+	}
101
+}
102
+
103
+func TestNormalizeInputs_AppliesDefault(t *testing.T) {
104
+	specs := []workflow.DispatchInput{{Name: "env", Type: "string", Default: "qa"}}
105
+	out, err := dispatch.NormalizeInputs(nil, specs)
106
+	if err != nil {
107
+		t.Fatalf("NormalizeInputs: %v", err)
108
+	}
109
+	if out["env"] != "qa" {
110
+		t.Errorf("default not applied: %+v", out)
111
+	}
112
+}
113
+
114
+func TestInputsFromForm_ParsesInputsPrefix(t *testing.T) {
115
+	v := url.Values{}
116
+	v.Set("ref", "trunk")
117
+	v.Set("inputs.env", "prod")
118
+	v.Set("inputs.debug", "true")
119
+	v.Set("inputs.", "ignored") // empty name → drop
120
+	got := dispatch.InputsFromForm(v)
121
+	if got["env"] != "prod" || got["debug"] != "true" {
122
+		t.Errorf("InputsFromForm: %+v", got)
123
+	}
124
+	if _, ok := got[""]; ok {
125
+		t.Errorf("empty input name leaked: %+v", got)
126
+	}
127
+}
128
+
129
+func TestInputsFromForm_NoInputsReturnsNil(t *testing.T) {
130
+	v := url.Values{"ref": {"trunk"}}
131
+	if got := dispatch.InputsFromForm(v); got != nil {
132
+		t.Errorf("expected nil, got %+v", got)
133
+	}
134
+}
135
+
136
+func TestValidWorkflowName(t *testing.T) {
137
+	good := []string{
138
+		".shithub/workflows/ci.yml",
139
+		".shithub/workflows/release.yaml",
140
+	}
141
+	bad := []string{
142
+		"",
143
+		".shithub/workflows/",
144
+		".shithub/workflows/ci.txt",
145
+		".shithub/workflows/sub/path.yml",
146
+		"foo.yml",
147
+		".shithub/workflows/ci.yml\x00",
148
+	}
149
+	for _, in := range good {
150
+		if !dispatch.ValidWorkflowName(in) {
151
+			t.Errorf("good input rejected: %q", in)
152
+		}
153
+	}
154
+	for _, in := range bad {
155
+		if dispatch.ValidWorkflowName(in) {
156
+			t.Errorf("bad input accepted: %q", in)
157
+		}
158
+	}
159
+}