-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathlifecycle_acceptance_test.go
More file actions
126 lines (116 loc) · 3.57 KB
/
Copy pathlifecycle_acceptance_test.go
File metadata and controls
126 lines (116 loc) · 3.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package gomeassistant
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/require"
)
func TestNewAppDoesNotMakeNetworkRequests(t *testing.T) {
var requests atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
requests.Add(1)
}))
defer server.Close()
app, err := NewApp(context.Background(), NewAppRequest{
URL: server.URL,
HAAuthToken: "token",
})
require.NoError(t, err)
require.Zero(t, requests.Load())
app.Cleanup()
require.Zero(t, requests.Load())
}
func TestCleanupWaitsForDeliveredCallbackAndRejectsLaterCallback(t *testing.T) {
callbackStarted := make(chan struct{})
releaseCallback := make(chan struct{})
var callbackCount atomic.Int32
upgrader := websocket.Upgrader{CheckOrigin: func(*http.Request) bool { return true }}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/states/zone.home" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"entity_id":"zone.home","state":"home","attributes":{"latitude":1,"longitude":2}}`))
return
}
if r.URL.Path != "/api/websocket" {
http.NotFound(w, r)
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
_ = conn.WriteJSON(map[string]string{"type": "auth_required"})
if _, _, err := conn.ReadMessage(); err != nil {
return
}
_ = conn.WriteJSON(map[string]string{"type": "auth_ok"})
for {
_, message, err := conn.ReadMessage()
if err != nil {
return
}
var request struct {
ID int64 `json:"id"`
EventType string `json:"event_type"`
}
if json.Unmarshal(message, &request) != nil || request.ID == 0 {
continue
}
_ = conn.WriteJSON(map[string]any{"id": request.ID, "type": "result", "success": true, "result": nil})
if request.EventType == "state_changed" {
_ = conn.WriteJSON(map[string]any{
"id": request.ID,
"type": "event",
"event": map[string]any{
"event_type": "state_changed",
"data": map[string]any{
"entity_id": "light.test",
"old_state": map[string]any{"state": "off"},
"new_state": map[string]any{"state": "on"},
},
},
})
}
}
}))
defer server.Close()
app := fakeApp(t, server.URL)
app.RegisterEntityListeners(NewEntityListener().EntityIDs("light.test").Call(func(*Service, State, EntityData) {
if callbackCount.Add(1) == 1 {
close(callbackStarted)
<-releaseCallback
}
}).Build())
runDone := make(chan error, 1)
go func() { runDone <- app.Start(context.Background()) }()
select {
case <-callbackStarted:
case <-time.After(time.Second):
t.Fatal("state-change callback was not delivered")
}
app.Cleanup()
require.ErrorIs(t, app.ctx.Err(), context.Canceled)
// This is the same delivered entity payload after cancellation; no second
// callback may start while Start waits for the first callback to finish.
app.callEntityListeners([]byte(`{"event":{"data":{"entity_id":"light.test","old_state":{"state":"off"},"new_state":{"state":"on"}}}}`))
require.Equal(t, int32(1), callbackCount.Load())
select {
case <-runDone:
t.Fatal("Start returned while the delivered callback was blocked")
case <-time.After(20 * time.Millisecond):
}
close(releaseCallback)
select {
case err := <-runDone:
require.ErrorIs(t, err, context.Canceled)
case <-time.After(time.Second):
t.Fatal("Start did not return after Cleanup")
}
require.Equal(t, int32(1), callbackCount.Load())
}