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  }