Skip to content

Commit a3b66a8

Browse files
committed
Check if assignment is allowed before creation
1 parent 236e717 commit a3b66a8

File tree

9 files changed

+312
-8
lines changed

9 files changed

+312
-8
lines changed
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
CREATE TABLE `settings` (
22
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
3-
`limit` int(11) DEFAULT NULL,
3+
`limit` int(11) NOT NULL DEFAULT 0,
44
`repeat` tinyint(1) NOT NULL DEFAULT '1',
5+
`singly` tinyint(1) NOT NULL DEFAULT '0',
6+
`whitelist` tinyint(1) NOT NULL DEFAULT '0',
57
`job_id` int(11) unsigned NOT NULL,
68
PRIMARY KEY (`id`),
7-
KEY `job_id` (`job_id`)
9+
UNIQUE KEY `job_id` (`job_id`)
810
)

pkg/api/assignmentcreator/endpoint.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ func makeAssignmentCreatorEndpoint(svc service.AssignmentService) endpoint.Endpo
1515
data, _ := authentication.ParseAuthData(ctx)
1616
svc.SetAuthData(data)
1717
req := request.(assignment.NewAssignment)
18-
saved, err := svc.CreateAssignment(req)
18+
settings, err := svc.GetSettings(req.JobID)
19+
if err != nil {
20+
return nil, errorResponse(err)
21+
}
22+
23+
saved, err := svc.CreateAssignment(req, settings)
1924
if err != nil {
2025
return nil, errorResponse(err)
2126
}

pkg/assignment/assignment.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,41 @@ type Assignment struct {
2424
}
2525

2626
type NewAssignment struct {
27-
JobID uint64 `json:"job_id"`
28-
TaskID uint64 `json:"task_id"`
29-
WorkerID uint64 `json:"worker_id"`
27+
JobID uint64 `json:"job_id"`
28+
TaskID uint64 `json:"task_id"`
29+
WorkerID uint64 `json:"worker_id"`
30+
JobAssignmentCount int `json:"job_assignment_count"`
31+
OnboardingStatus bool `json:"onboarding_status"`
32+
WorkerAlreadyAssigned bool `json:"worker_already_assigned"`
33+
WorkerAlreadyResponded bool `json:"worker_already_responded"`
34+
WorkerHasFunds bool `json:"worker_has_funds"`
3035
}
3136

3237
type Assignments []Assignment
38+
39+
func (a NewAssignment) IsAllowed(set *Settings) (bool, error) {
40+
// check onboarding
41+
if !a.OnboardingStatus {
42+
return false, OnboardingFailure{}
43+
}
44+
// We reached the limit of the total assignment for the job
45+
if set.Limit != 0 && set.Limit == a.JobAssignmentCount {
46+
return false, JobLimitReached{}
47+
}
48+
49+
// Only allow the worker to be once at a time
50+
if set.Singly && a.WorkerAlreadyAssigned {
51+
return false, NoAssignmentRepeat{}
52+
}
53+
54+
// Worker can only respond once for the the job
55+
if !set.Repeat && a.WorkerAlreadyResponded {
56+
return false, NoResponseRepeat{}
57+
}
58+
59+
if !a.WorkerHasFunds {
60+
return false, WorkerNotEnoughFunds{}
61+
}
62+
63+
return true, nil
64+
}

pkg/assignment/assignment_test.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package assignment
2+
3+
import "testing"
4+
5+
func TestNewAssignment_IsAllowed(t *testing.T) {
6+
limit := 2
7+
permutations := map[string]*Settings{
8+
"OneAssignmentRepeat": &Settings{
9+
ID: 1,
10+
JobID: 1,
11+
Limit: 0,
12+
Repeat: true,
13+
Singly: true,
14+
},
15+
"OneNoAssignmentRepeat": &Settings{
16+
ID: 1,
17+
JobID: 1,
18+
Limit: 0,
19+
Repeat: false,
20+
Singly: true,
21+
},
22+
"AllAssignmentRepeat": &Settings{
23+
ID: 1,
24+
JobID: 1,
25+
Limit: 0,
26+
Repeat: true,
27+
Singly: false,
28+
},
29+
"AssignmentWithLimit": &Settings{
30+
ID: 1,
31+
JobID: 1,
32+
Limit: limit,
33+
Repeat: true,
34+
Singly: false,
35+
},
36+
}
37+
38+
type args struct {
39+
set *Settings
40+
}
41+
tests := []struct {
42+
name string
43+
fields NewAssignment
44+
args args
45+
want bool
46+
wantErr bool
47+
}{
48+
{
49+
"singly assignment: returns true if worker is not assigned",
50+
MakeNewAssignment(false, false, true, true, 0),
51+
args{
52+
permutations["OneAssignmentRepeat"],
53+
},
54+
true,
55+
false,
56+
},
57+
{
58+
"singly assignment: returns true if worker is already assigned",
59+
MakeNewAssignment(true, false, true, true, 0),
60+
args{
61+
permutations["OneAssignmentRepeat"],
62+
},
63+
false,
64+
true,
65+
},
66+
{
67+
"singly assignment with repeats: returns true if worker has already responded",
68+
MakeNewAssignment(false, true, true, true, 0),
69+
args{
70+
permutations["OneAssignmentRepeat"],
71+
},
72+
true,
73+
false,
74+
},
75+
{
76+
"singly assignment without repeats: returns false if worker has already responded",
77+
MakeNewAssignment(false, true, true, true, 0),
78+
args{
79+
permutations["OneNoAssignmentRepeat"],
80+
},
81+
false,
82+
true,
83+
},
84+
{
85+
"multi assignment: returns true if worker has not been assigned",
86+
MakeNewAssignment(false, false, true, true, 0),
87+
args{
88+
permutations["AllAssignmentRepeat"],
89+
},
90+
true,
91+
false,
92+
},
93+
{
94+
"multi assignment: returns true if worker has been assigned",
95+
MakeNewAssignment(true, false, true, true, 0),
96+
args{
97+
permutations["AllAssignmentRepeat"],
98+
},
99+
true,
100+
false,
101+
},
102+
{
103+
"multi assignment: returns true if worker has responded but not assigned",
104+
MakeNewAssignment(false, true, true, true, 0),
105+
args{
106+
permutations["AllAssignmentRepeat"],
107+
},
108+
true,
109+
false,
110+
},
111+
{
112+
"multi assignment: returns true if worker has responded and assigned",
113+
MakeNewAssignment(true, true, true, true, 0),
114+
args{
115+
permutations["AllAssignmentRepeat"],
116+
},
117+
true,
118+
false,
119+
},
120+
{
121+
"returns true if job assignment limit has not been reached",
122+
MakeNewAssignment(false, false, true, true, limit-1),
123+
args{
124+
permutations["AssignmentWithLimit"],
125+
},
126+
true,
127+
false,
128+
},
129+
{
130+
"returns false if job assignment limit has been reached",
131+
MakeNewAssignment(false, false, true, true, limit),
132+
args{
133+
permutations["AssignmentWithLimit"],
134+
},
135+
false,
136+
true,
137+
},
138+
{
139+
"returns false if onboarding status is false",
140+
MakeNewAssignment(false, false, false, true, 0),
141+
args{
142+
permutations["AllAssignmentRepeat"],
143+
},
144+
false,
145+
true,
146+
},
147+
{
148+
"returns false if worker doesn't have enough funds",
149+
MakeNewAssignment(false, false, true, false, 0),
150+
args{
151+
permutations["AllAssignmentRepeat"],
152+
},
153+
false,
154+
true,
155+
},
156+
}
157+
for _, tt := range tests {
158+
t.Run(tt.name, func(t *testing.T) {
159+
a := NewAssignment{
160+
JobID: tt.fields.JobID,
161+
TaskID: tt.fields.TaskID,
162+
WorkerID: tt.fields.WorkerID,
163+
JobAssignmentCount: tt.fields.JobAssignmentCount,
164+
OnboardingStatus: tt.fields.OnboardingStatus,
165+
WorkerAlreadyAssigned: tt.fields.WorkerAlreadyAssigned,
166+
WorkerAlreadyResponded: tt.fields.WorkerAlreadyResponded,
167+
WorkerHasFunds: tt.fields.WorkerHasFunds,
168+
}
169+
got, err := a.IsAllowed(tt.args.set)
170+
if (err != nil) != tt.wantErr {
171+
t.Errorf("NewAssignment.IsAllowed() error = %v, wantErr %v", err, tt.wantErr)
172+
return
173+
}
174+
if got != tt.want {
175+
t.Errorf("NewAssignment.IsAllowed() = %v, want %v", got, tt.want)
176+
}
177+
})
178+
}
179+
}
180+
181+
func MakeNewAssignment(assigned, responded, onboarding, funds bool, aCount int) NewAssignment {
182+
return NewAssignment{
183+
JobID: 1,
184+
TaskID: 1,
185+
WorkerID: 1,
186+
JobAssignmentCount: aCount,
187+
OnboardingStatus: onboarding,
188+
WorkerAlreadyAssigned: assigned,
189+
WorkerAlreadyResponded: responded,
190+
WorkerHasFunds: funds,
191+
}
192+
}

pkg/assignment/errors.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package assignment
2+
3+
type JobLimitReached struct{}
4+
5+
func (err JobLimitReached) Error() string {
6+
return "We reached the limit of total job assignments"
7+
}
8+
9+
type NoAssignmentRepeat struct{}
10+
11+
func (err NoAssignmentRepeat) Error() string {
12+
return "Multiple assignments are not allowed"
13+
}
14+
15+
type NoResponseRepeat struct{}
16+
17+
func (err NoResponseRepeat) Error() string {
18+
return "Worker already responded to job"
19+
}
20+
21+
type OnboardingFailure struct{}
22+
23+
func (err OnboardingFailure) Error() string {
24+
return "Onboarding is either in progress or failed"
25+
}
26+
27+
type WorkerNotEnoughFunds struct{}
28+
29+
func (err WorkerNotEnoughFunds) Error() string {
30+
return "Worker doesn't have enough funds"
31+
}

pkg/assignment/settings.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package assignment
2+
3+
type Settings struct {
4+
ID uint64 `json:"id" db:"id"`
5+
JobID uint64 `json:"job_id" db:"job_id"` // unique, only one setting per job
6+
Limit int `json:"limit" db:"limit"` // total assignment limit per job (all workers combined)
7+
Repeat bool `json:"repeat" db:"repeat"` // can a worker repeat the job
8+
Singly bool `json:"singly" db:"singly"` // worker is only allowed to be assigned once at a time
9+
Whitelist bool `json:"whitelist" db:"whitelist"` // job has a whitelist for workers
10+
}

pkg/datastore/assignment.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Storage interface {
1313
GetAssignments(assignment.Params) (assignment.Assignments, error)
1414
GetAssignment(id string) (*assignment.Assignment, error)
1515
CreateAssignment(assignment.NewAssignment) (*assignment.Assignment, error)
16+
GetSettings(jobID uint64) (*assignment.Settings, error)
1617
}
1718

1819
type AssignmentStore struct {
@@ -58,6 +59,7 @@ func (as *AssignmentStore) GetAssignments(p assignment.Params) (assignment.Assig
5859

5960
return assignments, nil
6061
}
62+
6163
func (as *AssignmentStore) GetAssignment(id string) (*assignment.Assignment, error) {
6264
assignment := &assignment.Assignment{}
6365
err := as.DB.Get(assignment, "SELECT * FROM assignments WHERE id = ?", id)
@@ -92,3 +94,14 @@ func (as *AssignmentStore) CreateAssignment(a assignment.NewAssignment) (*assign
9294

9395
return assi, nil
9496
}
97+
98+
func (as *AssignmentStore) GetSettings(jobID uint64) (*assignment.Settings, error) {
99+
set := &assignment.Settings{}
100+
err := as.DB.Get(set, "SELECT * FROM settings WHERE job_id = ?", jobID)
101+
102+
if err != nil {
103+
return nil, err
104+
}
105+
106+
return set, nil
107+
}

pkg/nulls/int64.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package nulls
22

33
import (
44
"database/sql"
5+
"database/sql/driver"
56
"encoding/json"
67
)
78

@@ -45,3 +46,10 @@ func (n *Int64) UnmarshalJSON(data []byte) error {
4546
}
4647
return nil
4748
}
49+
50+
func (n *Int64) Value() (driver.Value, error) {
51+
if !n.Valid {
52+
return nil, nil
53+
}
54+
return n.Int64, nil
55+
}

pkg/service/service.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ type AssignmentService interface {
1212
SetAuthData(data authentication.AuthData)
1313
GetAssignments(assignment.Params) (assignment.Assignments, error)
1414
GetAssignment(id string) (*assignment.Assignment, error)
15-
CreateAssignment(assignment.NewAssignment) (*assignment.Assignment, error)
15+
CreateAssignment(assignment.NewAssignment, *assignment.Settings) (*assignment.Assignment, error)
16+
GetSettings(jobID uint64) (*assignment.Settings, error)
1617
}
1718

1819
type service struct {
@@ -43,6 +44,16 @@ func (s *service) GetAssignment(id string) (*assignment.Assignment, error) {
4344
return s.store.GetAssignment(id)
4445
}
4546

46-
func (s *service) CreateAssignment(a assignment.NewAssignment) (*assignment.Assignment, error) {
47+
func (s *service) CreateAssignment(a assignment.NewAssignment, set *assignment.Settings) (*assignment.Assignment, error) {
48+
// Check if the assignment is allowed
49+
allowed, err := a.IsAllowed(set)
50+
if !allowed {
51+
return nil, err
52+
}
53+
4754
return s.store.CreateAssignment(a)
4855
}
56+
57+
func (s *service) GetSettings(jobID uint64) (*assignment.Settings, error) {
58+
return s.store.GetSettings(jobID)
59+
}

0 commit comments

Comments
 (0)