import * as fs from "fs" ; import * as path from "path" ; import { BatchValidator } from "./BatchValidator" ; import { PrettyPrinter } from "./PrettyPrinter" ; import { SequencedValidator } from "./SequencedValidator" ; import { IVirtualDeviceTest , IVirtualDeviceTestSequence , IVirtualDeviceValidatorResult , IVirtualDeviceValidatorResultItem , VirtualDeviceValidator , } from "./VirtualDeviceValidator" ; import { YAMLParser } from "./YAMLParser" ; const invocationNameRegexp = /(open|launch|tell|ask)(.*)$/ ; const urlRegExp = /^https?:\/\//i ; export type IVirtualDeviceScriptCallback = ( error : Error , resultItem : IVirtualDeviceValidatorResultItem , context ? : any ) => void ; export class VirtualDeviceScript { private virtualDeviceValidator : VirtualDeviceValidator ; private findReplaceMap : { [ id ] : string ; } = { } ; constructor ( token ? , userID ? , batch = false ) { if ( batch ) { this . virtualDeviceValidator = new BatchValidator ( token , userID ) ; } else { this . virtualDeviceValidator = new SequencedValidator ( token , userID ) ; } } param { string } find param { string } replace public findReplace ( find , replace ) { this . findReplaceMap [ find ] = replace ; } public locale ( locale ) { this . virtualDeviceValidator . locale ( locale ) ; } public voiceID ( voiceID ) { return this . virtualDeviceValidator . voiceID ( voiceID ) ; } public tests ( scriptContents ) { scriptContents = scriptContents . trim ( ) ; scriptContents = this . tokenize ( scriptContents ) ; const sequences = [ ] ; let currentSequence = { tests : [ ] , invocationName : "" } ; let sequence = 1 ; let sequenceIndex = 1 ; let absoluteIndex = 0 ; const utteranceTests = new YAMLParser ( scriptContents ) . parse ( ) ; let utteranceCount = 0 ; let config = { } ; for ( const utteranceTest of utteranceTests ) { utteranceCount += 1 ; if ( utteranceTest . name ( ) === "config" ) { config = utteranceTest . object ( ) ; continue ; } if ( ! utteranceTest . isNull ( ) ) { absoluteIndex += 1 ; const input = utteranceTest . name ( ) as string ; const test = { absoluteIndex , comparison : "contains" , expected : { } , input , sequence , sequenceIndex , } ; const expected = test . expected ; if ( utteranceTest . isString ( ) ) { if ( utteranceTest . isEmpty ( ) ) { throw new Error ( "Line " + utteranceTest . line + ": No right-hand value specified." ) ; } if ( urlRegExp . test ( utteranceTest . string ( ) ) ) { expected . streamURL = utteranceTest . string ( ) ; } else { expected . transcript = utteranceTest . string ( ) ; } } else if ( utteranceTest . isArray ( ) ) { expected . transcript = utteranceTest . stringArray ( ) ; } else if ( utteranceTest . isObject ( ) ) { test . expected = utteranceTest . object ( ) ; } else if ( utteranceTest . value ( ) === undefined ) { throw new Error ( "Line " + utteranceTest . line + ": No properties added for object." ) ; } currentSequence . tests . push ( test ) ; sequenceIndex += 1 ; } if ( utteranceTest . isNull ( ) || utteranceCount === utteranceTests . length ) { if ( currentSequence . tests . length ) { sequence += 1 ; const firstInput = ( currentSequence . tests && currentSequence . tests . length > 0 && currentSequence . tests [ 0 ] && currentSequence . tests [ 0 ] . input ) || "" ; currentSequence . invocationName = this . detectInvocationName ( firstInput ) ; if ( config . voiceID ) { currentSequence . voiceID = config . voiceID ; } if ( config . locale ) { currentSequence . locale = config . locale ; } sequences . push ( { ... currentSequence } ) ; currentSequence = { tests : [ ] , invocationName : "" } ; sequenceIndex = 1 ; } } } return sequences ; } param { string } directoryPath public async executeDir ( directoryPath ) { directoryPath = path . resolve ( directoryPath ) ; let stats ; try { stats = fs . statSync ( directoryPath ) ; } catch ( e ) { return Promise . reject ( "Directory to execute does not exist: " + directoryPath ) ; } if ( ! stats . isDirectory ( ) ) { return Promise . reject ( "Not a directory: " + directoryPath ) ; } const items = fs . readdirSync ( directoryPath ) ; const results = { } ; for ( const filePath of items ) { if ( filePath . endsWith ( ".yml" ) || filePath . endsWith ( ".yaml" ) ) { const fullPath = path . join ( directoryPath , filePath ) ; const result = await this . executeFile ( fullPath ) ; results [ fullPath ] = result ; } } return results ; } public executeFile ( filePath ) { filePath = path . resolve ( filePath ) ; try { fs . statSync ( filePath ) ; } catch ( e ) { return Promise . reject ( "File to execute does not exist: " + filePath ) ; } const fileContents = fs . readFileSync ( filePath , "UTF-8" ) ; return this . execute ( fileContents ) ; } public execute ( scriptContents , context ? ) { return this . virtualDeviceValidator . execute ( this . tests ( scriptContents ) , context ) ; } public validate ( scriptContents ) { try { this . tests ( scriptContents ) ; return undefined ; } catch ( err ) { return err ; } } public prettifyAsPartialHTML ( scriptContents , partialResultItems , includeTimeContent ? ) { includeTimeContent = ( typeof includeTimeContent !== "undefined" ) ? includeTimeContent : true ; const virtualDeviceTestSequences = this . tests ( scriptContents ) ; const printer = new PrettyPrinter ( ) ; return printer . prettifyAsPartialHTML ( virtualDeviceTestSequences , partialResultItems , includeTimeContent ) ; } public prettifyAsHTML ( result , includeTimeContent ? ) { const printer = new PrettyPrinter ( ) ; return printer . prettifyAsHTML ( result , includeTimeContent ) ; } public on ( event , cb ) { this . virtualDeviceValidator . subscribe ( event , cb ) ; } public off ( event ) { this . virtualDeviceValidator . unsubscribe ( event ) ; } public checkAuth ( scriptContents ) { const sequences = this . tests ( scriptContents ) ; const promises = [ ] ; for ( const sequence of sequences ) { const promise = this . virtualDeviceValidator . checkAuth ( sequence . invocationName ) ; promises . push ( promise ) ; } return Promise . all ( promises ) . then ( ( ) => "AUTHORIZED" ) ; } private detectInvocationName ( input ) { const matches = input . toLowerCase ( ) . match ( invocationNameRegexp ) ; if ( ! matches || matches . length !== 3 ) { return "" ; } return matches [ 2 ] . trim ( ) ; } private tokenize ( script ) { for ( const find of Object . keys ( this . findReplaceMap ) ) { const value = this . findReplaceMap [ find ] ; script = script . split ( find ) . join ( value ) ; } return script ; } }