github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/realtime/realtime_test.go (about) 1 package realtime 2 3 import ( 4 "fmt" 5 "net/http" 6 "testing" 7 "time" 8 9 "github.com/cozy/cozy-stack/pkg/config/config" 10 "github.com/cozy/cozy-stack/pkg/realtime" 11 "github.com/cozy/cozy-stack/tests/testutils" 12 ) 13 14 type testDoc struct { 15 id string 16 doctype string 17 } 18 19 func TestRealtime(t *testing.T) { 20 if testing.Short() { 21 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 22 } 23 24 config.UseTestFile(t) 25 testutils.NeedCouchdb(t) 26 setup := testutils.NewSetup(t, t.Name()) 27 inst := setup.GetTestInstance() 28 _, token := setup.GetTestClient("io.cozy.foos io.cozy.bars io.cozy.bazs") 29 ts := setup.GetTestServer("/realtime", Routes) 30 t.Cleanup(ts.Close) 31 32 t.Run("WSNoAuth", func(t *testing.T) { 33 e := testutils.CreateTestClient(t, ts.URL) 34 35 ws := e.GET("/realtime/"). 36 WithWebsocketUpgrade(). 37 Expect().Status(http.StatusSwitchingProtocols). 38 Websocket() 39 defer ws.Disconnect() 40 41 obj := ws.WriteText(`{"method": "SUBSCRIBE", "payload": { "type": "io.cozy.foos" }}`). 42 Expect().TextMessage(). 43 JSON().Object() 44 45 obj.ValueEqual("event", "error") 46 payload := obj.Value("payload").Object() 47 payload.ValueEqual("status", "405 Method Not Allowed") 48 payload.ValueEqual("code", "method not allowed") 49 payload.ValueEqual("title", "The SUBSCRIBE method is not supported") 50 }) 51 52 t.Run("WSInvalidToken", func(t *testing.T) { 53 e := testutils.CreateTestClient(t, ts.URL) 54 55 ws := e.GET("/realtime/"). 56 WithWebsocketUpgrade(). 57 Expect().Status(http.StatusSwitchingProtocols). 58 Websocket() 59 defer ws.Disconnect() 60 61 obj := ws.WriteText(`{"method": "AUTH", "payload": "123456789"}`). 62 Expect().TextMessage(). 63 JSON().Object() 64 65 obj.ValueEqual("event", "error") 66 payload := obj.Value("payload").Object() 67 payload.ValueEqual("status", "401 Unauthorized") 68 payload.ValueEqual("code", "unauthorized") 69 payload.ValueEqual("title", "The authentication has failed") 70 }) 71 72 t.Run("WSNoPermissionsForADoctype", func(t *testing.T) { 73 e := testutils.CreateTestClient(t, ts.URL) 74 75 ws := e.GET("/realtime/"). 76 WithWebsocketUpgrade(). 77 Expect().Status(http.StatusSwitchingProtocols). 78 Websocket() 79 defer ws.Disconnect() 80 81 ws.WriteText(fmt.Sprintf(`{"method": "AUTH", "payload": "%s"}`, token)) 82 83 obj := ws.WriteText(`{"method": "SUBSCRIBE", "payload": { "type": "io.cozy.contacts" }}`). 84 Expect().TextMessage(). 85 JSON().Object() 86 87 obj.ValueEqual("event", "error") 88 payload := obj.Value("payload").Object() 89 payload.ValueEqual("status", "403 Forbidden") 90 payload.ValueEqual("code", "forbidden") 91 payload.ValueEqual("title", "The application can't subscribe to io.cozy.contacts") 92 }) 93 94 t.Run("WSSuccess", func(t *testing.T) { 95 e := testutils.CreateTestClient(t, ts.URL) 96 97 ws := e.GET("/realtime/"). 98 WithWebsocketUpgrade(). 99 Expect().Status(http.StatusSwitchingProtocols). 100 Websocket() 101 defer ws.Disconnect() 102 103 ws.WriteText(fmt.Sprintf(`{"method": "AUTH", "payload": "%s"}`, token)) 104 105 ws.WriteText(`{"method": "SUBSCRIBE", "payload": { "type": "io.cozy.foos" }}`) 106 ws.WriteText(`{"method": "SUBSCRIBE", "payload": { "type": "io.cozy.bars", "id": "bar-one" }}`) 107 ws.WriteText(`{"method": "SUBSCRIBE", "payload": { "type": "io.cozy.bars", "id": "bar-two" }}`) 108 109 h := realtime.GetHub() 110 time.Sleep(30 * time.Millisecond) 111 112 h.Publish(inst, realtime.EventUpdate, &testDoc{ 113 doctype: "io.cozy.foos", 114 id: "foo-one", 115 }, nil) 116 117 obj := ws.Expect().TextMessage().JSON().Object() 118 obj.ValueEqual("event", "UPDATED") 119 payload := obj.Value("payload").Object() 120 payload.ValueEqual("type", "io.cozy.foos") 121 payload.ValueEqual("id", "foo-one") 122 123 h.Publish(inst, realtime.EventDelete, &testDoc{ 124 doctype: "io.cozy.foos", 125 id: "foo-two", 126 }, nil) 127 128 obj = ws.Expect().TextMessage().JSON().Object() 129 obj.ValueEqual("event", "DELETED") 130 payload = obj.Value("payload").Object() 131 payload.ValueEqual("type", "io.cozy.foos") 132 payload.ValueEqual("id", "foo-two") 133 134 h.Publish(inst, realtime.EventCreate, &testDoc{ 135 doctype: "io.cozy.bars", 136 id: "bar-three", 137 }, nil) 138 // No event 139 140 h.Publish(inst, realtime.EventCreate, &testDoc{ 141 doctype: "io.cozy.bars", 142 id: "bar-one", 143 }, nil) 144 145 obj = ws.Expect().TextMessage().JSON().Object() 146 obj.ValueEqual("event", "CREATED") 147 payload = obj.Value("payload").Object() 148 payload.ValueEqual("type", "io.cozy.bars") 149 payload.ValueEqual("id", "bar-one") 150 151 ws.WriteText(`{"method": "UNSUBSCRIBE", "payload": { "type": "io.cozy.bars", "id": "bar-one" }}`) 152 time.Sleep(30 * time.Millisecond) 153 154 h.Publish(inst, realtime.EventUpdate, &testDoc{ 155 doctype: "io.cozy.bars", 156 id: "bar-one", 157 }, nil) 158 // No event 159 160 h.Publish(inst, realtime.EventUpdate, &testDoc{ 161 doctype: "io.cozy.bars", 162 id: "bar-two", 163 }, nil) 164 165 obj = ws.Expect().TextMessage().JSON().Object() 166 obj.ValueEqual("event", "UPDATED") 167 payload = obj.Value("payload").Object() 168 payload.ValueEqual("type", "io.cozy.bars") 169 payload.ValueEqual("id", "bar-two") 170 }) 171 172 t.Run("WSNotify", func(t *testing.T) { 173 e := testutils.CreateTestClient(t, ts.URL) 174 175 ws := e.GET("/realtime/"). 176 WithWebsocketUpgrade(). 177 Expect().Status(http.StatusSwitchingProtocols). 178 Websocket() 179 defer ws.Disconnect() 180 181 ws.WriteText(fmt.Sprintf(`{"method": "AUTH", "payload": "%s"}`, token)) 182 183 ws.WriteText(`{"method": "SUBSCRIBE", "payload": { "type": "io.cozy.bazs", "id": "baz-one" }}`) 184 time.Sleep(30 * time.Millisecond) 185 186 e.POST("/realtime/io.cozy.bazs/baz-one"). 187 WithHeader("Content-Type", "application/json"). 188 WithHeader("Authorization", "Bearer "+token). 189 WithBytes([]byte(`{ "hello": "world" }`)). 190 Expect().Status(204) 191 192 obj := ws.Expect().TextMessage().JSON().Object() 193 obj.ValueEqual("event", "NOTIFIED") 194 payload := obj.Value("payload").Object() 195 payload.ValueEqual("type", "io.cozy.bazs") 196 payload.ValueEqual("id", "baz-one") 197 198 doc := payload.Value("doc").Object() 199 doc.ValueEqual("hello", "world") 200 }) 201 } 202 203 func (t *testDoc) ID() string { return t.id } 204 func (t *testDoc) DocType() string { return t.doctype } 205 func (t *testDoc) MarshalJSON() ([]byte, error) { 206 j := `{"_id":"` + t.id + `", "_type":"` + t.doctype + `"}` 207 return []byte(j), nil 208 }