Skip to content

Commit 119051e

Browse files
committed
Add testing for Application Review
1 parent e2a4404 commit 119051e

File tree

7 files changed

+389
-45
lines changed

7 files changed

+389
-45
lines changed

.github/workflows/test-processor.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Test processor
2+
3+
on:
4+
push:
5+
paths:
6+
- "script/**"
7+
8+
jobs:
9+
test-processor:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout repository
13+
uses: actions/checkout@v4
14+
15+
- name: Setup Go
16+
uses: actions/setup-go@v5
17+
with:
18+
go-version-file: "script/go.mod"
19+
cache-dependency-path: "script/go.sum"
20+
21+
- name: Install dependencies
22+
run: make install_deps
23+
24+
- name: Test processor
25+
run: make test

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ build_processor:
88
$(info Building processor...)
99
@cd ./script && go build -v -o ../processor .
1010

11+
test:
12+
$(info Running tests...)
13+
@cd ./script && go test
14+
1115
bump_version:
1216
$(info Bumping version...)
1317
@$(eval LAST_TAG=$(shell git rev-list --tags='processor-*' --max-count=1 | xargs -r git describe --tags --match 'processor-*'))

script/application.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,18 @@ func (a *Application) Parse(issue github.Issue) {
7171

7272
isProject := !a.Project.IsTeam && !a.Project.IsEvent
7373

74-
a.Project.Name = a.stringSection("Project name", IsPresent, IsRegularString)
75-
a.Project.Description = a.stringSection("Short description", IsPresent, IsRegularString)
76-
a.Project.Contributors = a.intSection("Number of team members/core contributors", IsPresent, IsRegularString)
74+
a.Project.Name = a.stringSection("Project name", IsPresent, ParsePlainString)
75+
a.Project.Description = a.stringSection("Short description", IsPresent, ParsePlainString)
76+
a.Project.Contributors = a.intSection("Number of team members/core contributors", IsPresent, ParsePlainString)
7777
a.Project.HomeUrl = a.stringSection("Homepage URL", IsPresent, IsUrl)
7878
a.Project.RepoUrl = a.stringSection("Repository URL", IsUrl)
79-
a.Project.LicenseType = a.stringSection("License type", When(isProject, IsPresent), IsRegularString)
79+
a.Project.LicenseType = a.stringSection("License type", When(isProject, IsPresent), ParsePlainString)
8080
a.Project.LicenseUrl = a.stringSection("License URL", When(isProject, IsPresent), IsUrl)
8181
a.boolSection("Age confirmation", When(isProject, IsPresent), ParseCheckbox, When(isProject, IsChecked))
8282

83-
a.Applicant.Name = a.stringSection("Name", IsPresent, IsRegularString)
83+
a.Applicant.Name = a.stringSection("Name", IsPresent, ParsePlainString)
8484
a.Applicant.Email = a.stringSection("Email", IsPresent, IsEmail)
85-
a.Applicant.Role = a.stringSection("Project role", IsPresent, IsProjectRole)
85+
a.Applicant.Role = a.stringSection("Project role", IsPresent)
8686
a.Applicant.Id = *issue.User.ID
8787

8888
a.stringSection("Profile or website", IsUrl)

script/application_test.go

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
)
8+
9+
func errNoProjectName(sectionTitle string) error {
10+
return fmt.Errorf("%s: is missing project name", sectionTitle)
11+
}
12+
13+
func errIncomplete(sectionTitle string) error {
14+
return fmt.Errorf("%s: was not completed for application", sectionTitle)
15+
}
16+
17+
func errEmpty(sectionTitle string) error {
18+
return fmt.Errorf("%s: is empty", sectionTitle)
19+
}
20+
21+
func errMustBeChecked(sectionTitle string) error {
22+
return fmt.Errorf("%s: must be checked", sectionTitle)
23+
}
24+
25+
func errInvalidAccountUrl(sectionTitle string) error {
26+
return fmt.Errorf("%s: is invalid 1Password account URL", sectionTitle)
27+
}
28+
29+
func errContainsEmoji(sectionTitle string) error {
30+
return fmt.Errorf("%s: cannot contain emoji characters", sectionTitle)
31+
}
32+
33+
func errParsingNumber(sectionTitle string) error {
34+
return fmt.Errorf("%s: could not be parsed into a number", sectionTitle)
35+
}
36+
37+
func errInvalidUrl(sectionTitle string) error {
38+
return fmt.Errorf("%s: is an invalid URL", sectionTitle)
39+
}
40+
41+
func TestApplication(t *testing.T) {
42+
originalDir, err := os.Getwd()
43+
if err != nil {
44+
t.Fatalf("Failed to get current directory: %s", err)
45+
}
46+
47+
defer func() {
48+
if err := os.Chdir(originalDir); err != nil {
49+
t.Fatalf("Failed to change back to original directory: %s", err)
50+
}
51+
}()
52+
53+
if err := os.Chdir("../"); err != nil {
54+
t.Fatalf("Failed to change working directory: %s", err)
55+
}
56+
57+
testCases := []struct {
58+
name string
59+
expectedValid bool
60+
expectedProblems []error
61+
}{
62+
{
63+
name: "project",
64+
expectedValid: true,
65+
},
66+
{
67+
name: "team",
68+
expectedValid: true,
69+
},
70+
{
71+
name: "event",
72+
expectedValid: true,
73+
},
74+
{
75+
name: "empty-body",
76+
expectedValid: false,
77+
expectedProblems: []error{
78+
errIncomplete("Account URL"),
79+
errIncomplete("Non-commercial confirmation"),
80+
errIncomplete("Team application"),
81+
errIncomplete("Event application"),
82+
errIncomplete("Project name"),
83+
errIncomplete("Short description"),
84+
errIncomplete("Number of team members/core contributors"),
85+
errIncomplete("Homepage URL"),
86+
errIncomplete("Repository URL"),
87+
errIncomplete("License type"),
88+
errIncomplete("License URL"),
89+
errIncomplete("Age confirmation"),
90+
errIncomplete("Name"),
91+
errIncomplete("Email"),
92+
errIncomplete("Project role"),
93+
errIncomplete("Profile or website"),
94+
errIncomplete("Additional comments"),
95+
errIncomplete("Can we contact you?"),
96+
},
97+
},
98+
{
99+
name: "no-responses",
100+
expectedValid: false,
101+
expectedProblems: []error{
102+
errNoProjectName("Application title"),
103+
errEmpty("Account URL"),
104+
errMustBeChecked("Non-commercial confirmation"),
105+
errEmpty("Project name"),
106+
errEmpty("Short description"),
107+
errEmpty("Number of team members/core contributors"),
108+
errEmpty("Homepage URL"),
109+
errEmpty("License type"),
110+
errEmpty("License URL"),
111+
errMustBeChecked("Age confirmation"),
112+
errEmpty("Name"),
113+
errEmpty("Email"),
114+
errEmpty("Project role"),
115+
},
116+
},
117+
{
118+
name: "examples-1",
119+
expectedValid: false,
120+
expectedProblems: []error{
121+
errNoProjectName("Application title"),
122+
errInvalidAccountUrl("Account URL"),
123+
errMustBeChecked("Non-commercial confirmation"),
124+
errContainsEmoji("Project name"),
125+
errParsingNumber("Number of team members/core contributors"),
126+
errInvalidUrl("Homepage URL"),
127+
},
128+
},
129+
}
130+
131+
for _, tt := range testCases {
132+
t.Run(tt.name, func(t *testing.T) {
133+
application := Application{}
134+
135+
if tt.expectedValid {
136+
testIssueName = fmt.Sprintf("valid-%s", tt.name)
137+
} else {
138+
testIssueName = fmt.Sprintf("invalid-%s", tt.name)
139+
}
140+
141+
application.Parse(*getTestIssue())
142+
143+
if application.IsValid() != tt.expectedValid {
144+
if tt.expectedValid {
145+
t.Errorf("Test issue '%s' is invalid, expected valid", testIssueName)
146+
} else {
147+
t.Errorf("Test issue '%s' is valid, expected invalid", testIssueName)
148+
}
149+
}
150+
151+
if !tt.expectedValid && !errSliceEqual(application.Problems, tt.expectedProblems) {
152+
t.Errorf("Expected problems %v, got %v", tt.expectedProblems, application.Problems)
153+
}
154+
})
155+
}
156+
}

script/testing.go

+29
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,32 @@ func getTestIssue() *github.Issue {
4040

4141
return &issue
4242
}
43+
44+
func errSliceEqual(a, b []error) bool {
45+
if len(a) != len(b) {
46+
return false
47+
}
48+
49+
countA := make(map[string]int)
50+
countB := make(map[string]int)
51+
52+
for _, err := range a {
53+
countA[err.Error()]++
54+
}
55+
56+
for _, err := range b {
57+
countB[err.Error()]++
58+
}
59+
60+
if len(countA) != len(countB) {
61+
return false
62+
}
63+
64+
for k, v := range countA {
65+
if countB[k] != v {
66+
return false
67+
}
68+
}
69+
70+
return true
71+
}

script/validator.go

+26-39
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ var (
1717
accountUrlRegex = regexp.MustCompile(`^(https?:\/\/)?[\w.-]+\.1password\.(com|ca|eu)\/?$`)
1818
urlRegex = regexp.MustCompile(`https?://[^\s]+`)
1919
emailRegex = regexp.MustCompile(`[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}`)
20-
emojiRegex = regexp.MustCompile(`[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F800}-\x{1F8FF}\x{1F900}-\x{1F9FF}\x{1FA00}-\x{1FA6F}\x{1FA70}-\x{1FAFF}\x{1FB00}-\x{1FBFF}]+`)
21-
applicantRoles = []string{"Founder or Owner", "Team Member or Employee", "Project Lead", "Core Maintainer", "Developer", "Organizer or Admin", "Program Manager"}
20+
emojiRegex = regexp.MustCompile(`[\x{1F600}-\x{1F64F}\x{1F300}-\x{1F5FF}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F800}-\x{1F8FF}\x{1F900}-\x{1F9FF}\x{1FA00}-\x{1FA6F}\x{1FA70}-\x{1FAFF}\x{1FB00}-\x{1FBFF}]+`)
2221
)
2322

2423
type ValidationError struct {
@@ -54,8 +53,6 @@ func (v *Validator) HasError(section string) bool {
5453
return false
5554
}
5655

57-
// Parsing and validation utilities
58-
5956
func When(condition bool, callback ValidatorCallback) ValidatorCallback {
6057
if condition {
6158
return callback
@@ -74,6 +71,30 @@ func ParseInput(value string) (bool, string, string) {
7471
return true, value, ""
7572
}
7673

74+
func ParsePlainString(value string) (bool, string, string) {
75+
// strip all formattig, except for newlines
76+
html := blackfriday.Run([]byte(value))
77+
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(html))
78+
if err != nil {
79+
return false, value, err.Error()
80+
}
81+
value = strings.TrimSpace(doc.Text())
82+
83+
if urlRegex.MatchString(value) {
84+
return false, value, "cannot contain URLs"
85+
}
86+
87+
if emailRegex.MatchString(value) {
88+
return false, value, "cannot contain email addresses"
89+
}
90+
91+
if emojiRegex.MatchString(value) {
92+
return false, value, "cannot contain emoji characters"
93+
}
94+
95+
return true, value, ""
96+
}
97+
7798
func ParseAccountUrl(value string) (bool, string, string) {
7899
if accountUrlRegex.Match([]byte(value)) {
79100
if !strings.HasPrefix(value, "http://") && !strings.HasPrefix(value, "https://") {
@@ -87,7 +108,7 @@ func ParseAccountUrl(value string) (bool, string, string) {
87108

88109
return true, u.Hostname(), ""
89110
} else {
90-
return false, value, "is an invalid 1Password account URL"
111+
return false, value, "is invalid 1Password account URL"
91112
}
92113
}
93114

@@ -168,40 +189,6 @@ func IsUrl(value string) (bool, string, string) {
168189
return true, value, ""
169190
}
170191

171-
func IsRegularString(value string) (bool, string, string) {
172-
// strip all formattig, except for newlines
173-
html := blackfriday.Run([]byte(value))
174-
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(html))
175-
if err != nil {
176-
return false, value, err.Error()
177-
}
178-
value = strings.TrimSpace(doc.Text())
179-
180-
if urlRegex.MatchString(value) {
181-
return false, value, "cannot contain URLs"
182-
}
183-
184-
if emailRegex.MatchString(value) {
185-
return false, value, "cannot contain email addresses"
186-
}
187-
188-
if emojiRegex.MatchString(value) {
189-
return false, value, "cannot contain emoji characters"
190-
}
191-
192-
return true, value, ""
193-
}
194-
195-
func IsProjectRole(value string) (bool, string, string) {
196-
for _, item := range applicantRoles {
197-
if item == value {
198-
return true, value, ""
199-
}
200-
}
201-
202-
return false, value, "is an invalid project role"
203-
}
204-
205192
func IsChecked(value string) (bool, string, string) {
206193
if value != "true" {
207194
return false, value, "must be checked"

0 commit comments

Comments
 (0)