Skip to content

Commit f68b5f2

Browse files
Refactor towards a process (#7)
- Removed all implementation related to output messages and replaced by a `RegisterAnPollProcess` type - Renamed param _delay_ by _sec_ in CLI command `./timehook -sec`
1 parent 5cd6e44 commit f68b5f2

8 files changed

Lines changed: 461 additions & 436 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ Timehook CLI client is a client implementation to use with [https://api.timehook
66

77
## Usage
88

9+
##### Use executable (recommended)
10+
11+
Download at [releases](https://github.com/timehook/cli-client/releases)
12+
913
##### Compile on your own
1014

1115
1. Download or clone the repo.
@@ -24,7 +28,7 @@ With defaults:
2428

2529
With custom values:
2630

27-
./bin/timehook --delay 11 --url https://your-url.com body --body '{"bar" : "bar"}'
31+
./bin/timehook --sec 11 --url https://your-url.com body --body '{"bar" : "bar"}'
2832
2933
3034
For further info:

cmd/main/timehook.go

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
func main() {
1414
URL := flag.String("url", "https://httpstat.us/200", "webhook URL")
1515
body := flag.String("body", `{"msg" : "from timehook client"}`, "webhook body in JSON")
16-
delay := flag.Int("delay", 5, "delay in seconds")
16+
sec := flag.Int("sec", 5, "delay in seconds")
1717
flag.Parse()
1818

1919
if os.Getenv("TIMEHOOK_KEY") == "" {
@@ -22,18 +22,13 @@ func main() {
2222
}
2323

2424
client := timehook.New(os.Getenv("TIMEHOOK_KEY"), http.DefaultClient)
25-
out, errc, succ := client.RegisterAndPoll(*URL, *body, *delay, 1*time.Second)
26-
for {
27-
select {
28-
case msg := <-out:
29-
fmt.Fprint(os.Stdout, msg)
30-
case msg := <-errc:
31-
fmt.Fprint(os.Stderr, msg)
32-
case isSucc := <-succ:
33-
if isSucc {
34-
os.Exit(0)
35-
}
36-
os.Exit(1)
37-
}
25+
proc := client.RegisterAndPoll(*URL, *body, *sec, 1*time.Second)
26+
for msg := range proc.C {
27+
fmt.Fprint(os.Stdout, msg)
3828
}
29+
30+
if proc.IsSucceeded() {
31+
os.Exit(0)
32+
}
33+
os.Exit(1)
3934
}

timehook/client.go

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ type StateResponse struct {
4141
}
4242

4343
var (
44-
errTooManyRequests = errors.New("server responses 429 too many request")
45-
errUnauthorized = errors.New("server responses 401 unauthorized request")
44+
ErrTooManyRequests = errors.New("server responses 429 too many request")
45+
ErrUnauthorized = errors.New("server responses 401 unauthorized request")
4646
)
4747

4848
// client exposes the functions allowed to talk with the Timehook API
@@ -52,29 +52,19 @@ type client struct {
5252
}
5353

5454
// RegisterAndPoll starts a long running process for a single webhook and
55-
// returns three channels: first one for regular messages, second one for
56-
// errors and third one indicates in the overall process was successful.
55+
// returns RegisterAnPollProcess which can be query to know the process.
5756
//
5857
// The process consists in first registers the webhook to be execute on URL
5958
// with the body given with a delay in seconds.
6059
// Second it polls the state until it the webhook finishes or until encounter
6160
// an irrecoverable error.
62-
func (c *client) RegisterAndPoll(URL, body string, delay int, interval time.Duration) (<-chan string, <-chan string, <-chan bool) {
63-
out := make(chan string)
64-
errc := make(chan string)
65-
succ := make(chan bool)
66-
61+
func (c *client) RegisterAndPoll(URL, body string, sec int, interval time.Duration) *RegisterAnPollProcess {
62+
proc := NewRegisterAnPollProcess()
6763
go func() {
68-
defer close(out)
69-
defer close(errc)
70-
defer close(succ)
71-
stdout := NewOutput()
72-
73-
out <- stdout.Connecting()
74-
rr, err := c.register(URL, body, delay)
64+
proc.Connect()
65+
rr, err := c.register(URL, body, sec)
7566
if err != nil {
76-
errc <- stdout.Error(err)
77-
succ <- false
67+
proc.Error(err)
7868
return
7969
}
8070

@@ -83,21 +73,18 @@ func (c *client) RegisterAndPoll(URL, body string, delay int, interval time.Dura
8373
for range ticker.C {
8474
sr, err := c.state(rr.ID)
8575
if err != nil {
86-
errc <- stdout.Error(err)
76+
proc.Error(err)
8777
} else {
88-
for _, v := range stdout.State(sr) {
89-
out <- v
90-
}
78+
proc.State(sr)
9179
}
9280

93-
if isFinal(sr, err) {
94-
succ <- isSuccess(sr, err)
81+
if proc.IsFinished() {
9582
break
9683
}
9784
}
9885
}()
9986

100-
return out, errc, succ
87+
return proc
10188
}
10289

10390
// isFinal returns if StateResponse or err is a final state
@@ -106,7 +93,7 @@ func isFinal(state *StateResponse, err error) bool {
10693
return state.Status == "failed" || state.Status == "succeeded" || state.Status == "timeout"
10794
}
10895

109-
if err == errUnauthorized {
96+
if err == ErrUnauthorized {
11097
return true
11198
}
11299

@@ -180,9 +167,9 @@ func (c *client) execute(req *http.Request, codeWanted int) ([]byte, error) {
180167

181168
switch c := resp.StatusCode; {
182169
case c == 401:
183-
return nil, errUnauthorized
170+
return nil, ErrUnauthorized
184171
case c == 429:
185-
return nil, errTooManyRequests
172+
return nil, ErrTooManyRequests
186173
case c != codeWanted:
187174
return nil, fmt.Errorf("wrong response registering webhook: %s, %s\n", resp.Status, b)
188175
}

timehook/client_test.go

Lines changed: 3 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,14 @@
11
package timehook_test
22

33
import (
4+
"io/ioutil"
45
"testing"
56
"time"
67

7-
"errors"
8-
9-
"io/ioutil"
10-
118
"github.com/timehook/cli-client/mock"
129
"github.com/timehook/cli-client/timehook"
1310
)
1411

15-
func TestRegisterAndPoll_Channels(t *testing.T) {
16-
tt := []struct {
17-
name string
18-
httpResponses []interface{}
19-
outWanted int
20-
errWanted int
21-
succWanted bool
22-
}{
23-
{
24-
name: "happy path",
25-
httpResponses: []interface{}{
26-
mock.RegisteredSuccess(),
27-
mock.StateRegistered(),
28-
mock.StateAwaiting(),
29-
mock.StateSending(),
30-
mock.StateSucceeded(),
31-
},
32-
outWanted: 5,
33-
errWanted: 0,
34-
succWanted: true,
35-
},
36-
{
37-
name: "succeeded with error querying state",
38-
httpResponses: []interface{}{
39-
mock.RegisteredSuccess(),
40-
mock.StateRegistered(),
41-
errors.New("some error"),
42-
mock.StateAwaiting(),
43-
mock.StateSending(),
44-
mock.StateSucceeded(),
45-
},
46-
outWanted: 5,
47-
errWanted: 1,
48-
succWanted: true,
49-
},
50-
{
51-
name: "not succeeded with unauthorized error",
52-
httpResponses: []interface{}{
53-
mock.Unauthorized(),
54-
},
55-
outWanted: 1,
56-
errWanted: 1,
57-
succWanted: false,
58-
},
59-
{
60-
name: "not succeeded with failed webhook",
61-
httpResponses: []interface{}{
62-
mock.RegisteredSuccess(),
63-
mock.StateRegistered(),
64-
mock.StateAwaiting(),
65-
mock.StateSending(),
66-
mock.StateFailed(),
67-
},
68-
outWanted: 5,
69-
errWanted: 0,
70-
succWanted: false,
71-
},
72-
{
73-
name: "succeeded with 429 too many requests",
74-
httpResponses: []interface{}{
75-
mock.RegisteredSuccess(),
76-
mock.StateRegistered(),
77-
mock.StateAwaiting(),
78-
mock.TooManyRequest429(),
79-
mock.StateSending(),
80-
mock.StateFailed(),
81-
},
82-
outWanted: 5,
83-
errWanted: 1,
84-
succWanted: false,
85-
},
86-
}
87-
88-
for _, v := range tt {
89-
t.Run(v.name, func(t *testing.T) {
90-
client := timehook.New("api-key", mock.HTTPClient(v.httpResponses))
91-
out, errc, succ := client.RegisterAndPoll("https://the-domain.com", `{"foo" : "bar"}`, 5, 1*time.Nanosecond)
92-
timeC := time.After(1 * time.Second)
93-
var outN, errN int
94-
loop:
95-
for {
96-
select {
97-
case <-out:
98-
outN++
99-
case <-errc:
100-
errN++
101-
case isSucc := <-succ:
102-
if isSucc != v.succWanted {
103-
t.Errorf("wrong succ, got %v want %v", isSucc, v.succWanted)
104-
}
105-
break loop
106-
case <-timeC:
107-
t.Errorf("no message receive in succ channel")
108-
}
109-
}
110-
111-
if outN != v.outWanted {
112-
t.Errorf("wrong number of out messages, want %d got %d", v.outWanted, outN)
113-
}
114-
if errN != v.errWanted {
115-
t.Errorf("wrong number of err messages, want %d got %d", v.errWanted, errN)
116-
}
117-
})
118-
}
119-
}
120-
12112
func TestRegisterAndPoll_HTTPRequest(t *testing.T) {
12213
// given
12314
HTTPClient := mock.HTTPClient([]interface{}{
@@ -130,15 +21,8 @@ func TestRegisterAndPoll_HTTPRequest(t *testing.T) {
13021
client := timehook.New("api-key", HTTPClient)
13122

13223
// when
133-
out, errc, succ := client.RegisterAndPoll("https://the-domain.com", `{"foo" : "bar"}`, 5, 1*time.Nanosecond)
134-
loop:
135-
for {
136-
select {
137-
case <-out:
138-
case <-errc:
139-
case <-succ:
140-
break loop
141-
}
24+
proc := client.RegisterAndPoll("https://the-domain.com", `{"foo" : "bar"}`, 5, 1*time.Nanosecond)
25+
for range proc.C {
14226
}
14327

14428
// then register

timehook/ouput.go

Lines changed: 0 additions & 99 deletions
This file was deleted.

0 commit comments

Comments
 (0)