github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/tests/plugins/alexa/alexa_test.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package alexa 20 21 import ( 22 "context" 23 "encoding/json" 24 "testing" 25 "time" 26 27 . "github.com/smartystreets/goconvey/convey" 28 29 "github.com/e154/smart-home/adaptors" 30 "github.com/e154/smart-home/common" 31 m "github.com/e154/smart-home/models" 32 "github.com/e154/smart-home/plugins/alexa" 33 "github.com/e154/smart-home/system/bus" 34 "github.com/e154/smart-home/system/scripts" 35 "github.com/e154/smart-home/system/supervisor" 36 . "github.com/e154/smart-home/tests/plugins" 37 ) 38 39 func TestAlexa(t *testing.T) { 40 41 const ( 42 skillScriptSrc = ` 43 skillOnLaunch = -> 44 #print '---action onLaunch---' 45 Done('skillOnLaunch') 46 skillOnSessionEnd = -> 47 #print '---action onSessionEnd---' 48 Done('skillOnSessionEnd') 49 skillOnIntent = -> 50 #print '---action onIntent---' 51 state = 'on' 52 if Alexa.slots['state'] == 'off' 53 state = 'off' 54 55 place = Alexa.slots['place'] 56 57 Done("#{place}_#{state}") 58 59 Alexa.sendMessage("#{place}_#{state}") 60 ` 61 62 launchRequest = `{ 63 "version": "1.0", 64 "session": { 65 "new": true, 66 "sessionId": "...", 67 "application": { 68 "applicationId": "amzn1.ask.skill.1ccc278b-ffbf-440c-87e3-83349761fbab" 69 }, 70 "user": { 71 "userId": "..." 72 } 73 }, 74 "context": { 75 76 "Extensions": { 77 "available": { 78 "aplext:backstack:10": {} 79 } 80 }, 81 "System": { 82 "application": { 83 "applicationId": "amzn1.ask.skill.1ccc278b-ffbf-440c-87e3-83349761fbab" 84 }, 85 "user": { 86 "userId": "..." 87 }, 88 "device": { 89 "deviceId": "...", 90 "supportedInterfaces": {} 91 }, 92 "apiEndpoint": "https://api.amazonalexa.com", 93 "apiAccessToken": "..." 94 } 95 }, 96 "request": { 97 "type": "LaunchRequest", 98 "requestId": "amzn1.echo-api.request.122e6887-0ddb-4781-ba88-67e15e928209", 99 "locale": "en-US", 100 "timestamp": "2021-05-13T16:35:16Z", 101 "shouldLinkResultBeReturned": false 102 } 103 }` 104 105 intentRequest = `{ 106 "version": "1.0", 107 "session": { 108 "new": false, 109 "sessionId": "amzn1.echo-api.session.b7bcc77c-0165-47d0-a9b3-dfbc4f21b65b", 110 "application": { 111 "applicationId": "amzn1.ask.skill.1ccc278b-ffbf-440c-87e3-83349761fbab" 112 }, 113 "user": { 114 "userId": "..." 115 } 116 }, 117 "context": { 118 "Extensions": { 119 "available": { 120 "aplext:backstack:10": {} 121 } 122 }, 123 "System": { 124 "application": { 125 "applicationId": "amzn1.ask.skill.1ccc278b-ffbf-440c-87e3-83349761fbab" 126 }, 127 "user": { 128 "userId": "..." 129 }, 130 "device": { 131 "deviceId": "...", 132 "supportedInterfaces": {} 133 }, 134 "apiEndpoint": "https://api.amazonalexa.com", 135 "apiAccessToken": "..." 136 } 137 }, 138 "request": { 139 "type": "IntentRequest", 140 "requestId": "amzn1.echo-api.request.3c295be0-3b79-49a6-a274-b41162e17b52", 141 "locale": "en-US", 142 "timestamp": "2021-05-15T05:39:46Z", 143 "intent": { 144 "name": "FlatLights", 145 "confirmationStatus": "NONE", 146 "slots": { 147 "state": { 148 "name": "state", 149 "value": "on", 150 "resolutions": { 151 "resolutionsPerAuthority": [ 152 { 153 "authority": "...", 154 "status": { 155 "code": "ER_SUCCESS_MATCH" 156 }, 157 "values": [ 158 { 159 "value": { 160 "name": "on", 161 "id": "ed2b5c0139cec8ad2873829dc1117d50" 162 } 163 } 164 ] 165 } 166 ] 167 }, 168 "confirmationStatus": "NONE", 169 "source": "USER", 170 "slotValue": { 171 "type": "Simple", 172 "value": "on", 173 "resolutions": { 174 "resolutionsPerAuthority": [ 175 { 176 "authority": "...", 177 "status": { 178 "code": "ER_SUCCESS_MATCH" 179 }, 180 "values": [ 181 { 182 "value": { 183 "name": "on", 184 "id": "ed2b5c0139cec8ad2873829dc1117d50" 185 } 186 } 187 ] 188 } 189 ] 190 } 191 } 192 }, 193 "place": { 194 "name": "place", 195 "value": "kitchen", 196 "resolutions": { 197 "resolutionsPerAuthority": [ 198 { 199 "authority": "...", 200 "status": { 201 "code": "ER_SUCCESS_MATCH" 202 }, 203 "values": [ 204 { 205 "value": { 206 "name": "kitchen", 207 "id": "09228dac155633b13780552bc01dc2e0" 208 } 209 } 210 ] 211 } 212 ] 213 }, 214 "confirmationStatus": "NONE", 215 "source": "USER", 216 "slotValue": { 217 "type": "Simple", 218 "value": "kitchen", 219 "resolutions": { 220 "resolutionsPerAuthority": [ 221 { 222 "authority": "...", 223 "status": { 224 "code": "ER_SUCCESS_MATCH" 225 }, 226 "values": [ 227 { 228 "value": { 229 "name": "kitchen", 230 "id": "09228dac155633b13780552bc01dc2e0" 231 } 232 } 233 ] 234 } 235 ] 236 } 237 } 238 } 239 } 240 } 241 } 242 }` 243 ) 244 245 Convey("alexa", t, func(ctx C) { 246 _ = container.Invoke(func(adaptors *adaptors.Adaptors, 247 scriptService scripts.ScriptService, 248 supervisor supervisor.Supervisor, 249 eventBus bus.Bus) { 250 251 // register plugins 252 AddPlugin(adaptors, "triggers") 253 AddPlugin(adaptors, "alexa") 254 255 var lastVal string 256 scriptService.PushFunctions("Done", func(args string) { 257 lastVal = args 258 }) 259 260 // common 261 // ------------------------------------------------ 262 ch := make(chan alexa.EventAlexaAction) 263 defer close(ch) 264 fn := func(_ string, m interface{}) { 265 switch v := m.(type) { 266 case alexa.EventAlexaAction: 267 ch <- v 268 } 269 } 270 eventBus.Subscribe(alexa.TopicPluginAlexa, fn) 271 defer eventBus.Unsubscribe(alexa.TopicPluginAlexa, fn) 272 273 // add scripts 274 // ------------------------------------------------ 275 276 alexaSkillScript, err := AddScript("alexa skill script", skillScriptSrc, adaptors, scriptService) 277 So(err, ShouldBeNil) 278 279 // add alexa skills 280 // ------------------------------------------------ 281 282 skill := &m.AlexaSkill{ 283 SkillId: "amzn1.ask.skill.1ccc278b-ffbf-440c-87e3-83349761fbab", 284 Description: "flat lights", 285 Status: "enabled", 286 ScriptId: common.Int64(alexaSkillScript.Id), 287 } 288 skill.Id, err = adaptors.AlexaSkill.Add(context.Background(), skill) 289 So(err, ShouldBeNil) 290 291 intent := &m.AlexaIntent{ 292 Name: "FlatLights", 293 AlexaSkillId: skill.Id, 294 ScriptId: alexaSkillScript.Id, 295 } 296 err = adaptors.AlexaIntent.Add(context.Background(), intent) 297 So(err, ShouldBeNil) 298 299 // ------------------------------------------------ 300 301 serviceCh := WaitService(eventBus, time.Second*5, "Supervisor") 302 supervisor.Start(context.Background()) 303 defer supervisor.Shutdown(context.Background()) 304 So(<-serviceCh, ShouldBeTrue) 305 306 // ------------------------------------------------ 307 plugin, err := supervisor.GetPlugin("alexa") 308 So(err, ShouldBeNil) 309 310 alexaPlugin, ok := plugin.(alexa.AlexaPlugin) 311 So(ok, ShouldBeTrue) 312 313 server := alexaPlugin.Server() 314 315 t.Run("on launch", func(t *testing.T) { 316 req := &alexa.Request{} 317 err = json.Unmarshal([]byte(launchRequest), req) 318 ctx.So(err, ShouldBeNil) 319 320 resp := alexa.NewResponse() 321 server.OnLaunchHandler(nil, req, resp) 322 323 ctx.So(lastVal, ShouldEqual, "skillOnLaunch") 324 }) 325 326 t.Run("on intent", func(t *testing.T) { 327 328 req := &alexa.Request{} 329 err = json.Unmarshal([]byte(intentRequest), req) 330 ctx.So(err, ShouldBeNil) 331 332 resp := alexa.NewResponse() 333 server.OnIntentHandle(nil, req, resp) 334 335 ctx.So(lastVal, ShouldEqual, "kitchen_on") 336 337 // wait message 338 msg, ok := WaitT[alexa.EventAlexaAction](time.Second*2, ch) 339 ctx.So(ok, ShouldBeTrue) 340 341 ctx.So(msg.Payload, ShouldEqual, "kitchen_on") 342 ctx.So(msg.SkillId, ShouldEqual, 1) 343 ctx.So(msg.IntentName, ShouldEqual, "FlatLights") 344 }) 345 }) 346 }) 347 }