text: // -*- mode: js; indent-tabs-mode: nil; js-basic-offset: 4 -*-
  //
  // This file is part of Almond
  //
  // Copyright 2019-2020 The Board of Trustees of the Leland Stanford Junior University
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //    http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  //
  // Author: Giovanni Campagna <gcampagn@cs.stanford.edu>
  "use strict";
  
  const assert = require('assert');
  const WebSocket = require('ws');
  const { assertHttpError, request, sessionRequest } = require('./scaffold');
  const { login, } = require('./login');
  
  async function getAccessToken(session) {
      return JSON.parse(await sessionRequest('/user/token', 'POST', '', session, {
          accept: 'application/json',
      })).token;
  }
  
  async function testMyApiInvalid(auth) {
      await assertHttpError(request('/api/invalid', 'GET', null, { auth }), 404);
  }
  
  async function testMyApiCreateGetApp(auth) {
      const result = JSON.parse(await request('/api/apps/create', 'POST', JSON.stringify({
          code: `now => @org.thingpedia.builtin.test(id="org.thingpedia.builtin.test").get_data(count=2, size=10byte) => notify;`
      }), { auth, dataContentType: 'application/json' }));
  
      assert(result.uniqueId.startsWith('uuid-'));
      assert.strictEqual(result.description, 'Get get data on test with count 2 and size 10 byte.');
      assert.strictEqual(result.code, '@org.thingpedia.builtin.test.get_data(count=2, size=10byte);');
      assert.strictEqual(result.icon, 'https://thingpedia.stanford.edu/thingpedia/api/v3/devices/icon/org.thingpedia.builtin.test');
      assert.deepStrictEqual(result.errors, []);
  
      assert.deepStrictEqual(result.results, [{
          raw: {
              count: 2,
              data: '!!!!!!!!!!',
              size: 10
          },
          formatted: [{ type: 'text', text: 'The answer is !!!!!!!!!!' }],
          type: 'org.thingpedia.builtin.test:get_data'
      }, {
          raw: {
              count: 2,
              data: '""""""""""',
              size: 10
          },
          formatted: [{ type: 'text', text: 'The answer is """""""""".' }],
          type: 'org.thingpedia.builtin.test:get_data'
      }]);
  }
  
  function awaitConnect(ws) {
      return new Promise((resolve, reject) => {
          ws.on('open', resolve);
      });
  }
  
  async function testMyApiCreateWhenApp(auth) {
      const ws = new WebSocket('http://127.0.0.1:3000/api/results', {
          headers: {
              'Authorization': auth
          }
      });
      await awaitConnect(ws);
  
      const result = JSON.parse(await request('/api/apps/create', 'POST', JSON.stringify({
          code: `monitor(@org.thingpedia.builtin.test(id="org.thingpedia.builtin.test").get_data(size=10byte)) => notify;`
      }), { auth, dataContentType: 'application/json' }));
  
      assert(result.uniqueId.startsWith('uuid-'));
      assert.strictEqual(result.description, 'Notify me when there are new get data on test with size 10 byte.');
      assert.strictEqual(result.code, 'monitor(@org.thingpedia.builtin.test.get_data(size=10byte));');
      assert.strictEqual(result.icon, 'https://thingpedia.stanford.edu/thingpedia/api/v3/devices/icon/org.thingpedia.builtin.test');
      assert.deepStrictEqual(result.results, []);
      assert.deepStrictEqual(result.errors, []);
  
      await new Promise((resolve, reject) => {
          let count = 0;
          ws.on('message', (data) => {
              const parsed = JSON.parse(data);
              if (parsed.result.appId !== result.uniqueId)
                  return;
              delete parsed.result.raw.__timestamp;
              console.log(data);
              if (count === 0) {
                  assert.deepStrictEqual(parsed, {
                      result: {
                          appId: result.uniqueId,
                          raw: { data: '!!!!!!!!!!', size: 10 },
                          type: 'org.thingpedia.builtin.test:get_data',
                          formatted: [{ type: 'text', text: 'Notification from Test: the answer is !!!!!!!!!!' }],
                          icon: 'https://thingpedia.stanford.edu/thingpedia/api/v3/devices/icon/org.thingpedia.builtin.test'
                      }
                  });
              } else {
                  assert.deepStrictEqual(parsed, {
                      result: {
                          appId: result.uniqueId,
                          raw: { data: '""""""""""', size: 10 },
                          type: 'org.thingpedia.builtin.test:get_data',
                          formatted: [{ type: 'text', text: 'Notification from Test: the answer is """""""""".' }],
                          icon: 'https://thingpedia.stanford.edu/thingpedia/api/v3/devices/icon/org.thingpedia.builtin.test'
                      }
                  });
              }
              if (++count === 2) {
                  ws.close();
                  resolve();
              }
          });
      });
  
      return result.uniqueId;
  }
  
  async function testMyApiListApps(auth, uniqueId) {
      const listResult = JSON.parse(await request('/api/apps/list', 'GET', null, { auth }));
      assert.deepStrictEqual(listResult, [{
          uniqueId,
          name: 'Test',
          description: 'Notify me when there are new get data on test with size 10 byte.',
          error: null,
          code: 'monitor(@org.thingpedia.builtin.test.get_data(size=10byte));',
          icon: 'org.thingpedia.builtin.test',
          isEnabled: true,
          isRunning: true,
      }]);
  
      const getResult = JSON.parse(await request('/api/apps/get/' + uniqueId, 'GET', null, { auth }));
      assert.deepStrictEqual(getResult, {
          uniqueId,
          name: 'Test',
          description: 'Notify me when there are new get data on test with size 10 byte.',
          error: null,
          code: 'monitor(@org.thingpedia.builtin.test.get_data(size=10byte));',
          icon: 'org.thingpedia.builtin.test',
          isEnabled: true,
          isRunning: true,
      });
  
      await assertHttpError(request('/api/apps/get/uuid-invalid', 'GET', null, { auth }), 404);
  }
  
  async function testMyApiDeleteApp(auth, uniqueId) {
      const result = JSON.parse(await request('/api/apps/delete/' + uniqueId, 'POST', '', { auth }));
      assert.deepStrictEqual(result, { status: 'ok' });
  
      const listResult = JSON.parse(await request('/api/apps/list', 'GET', null, { auth }));
      assert.deepStrictEqual(listResult, []);
  
      await assertHttpError(request('/api/apps/delete/uuid-invalid', 'POST', '', { auth }), 404);
  }
  
  async function testMyApiDevices(auth) {
      const listResult = JSON.parse(await request('/api/devices/list', 'GET', null, { auth }));
  
      assert.deepStrictEqual(listResult, [
        { uniqueId: 'thingengine-own-server:81e0e8abba27202a',
          name: 'Almond server (:81e0e8abba27202a)',
          description: 'This is one of your own Almond apps.',
          kind: 'org.thingpedia.builtin.thingengine',
          version: 0,
          ownerTier: 'server',
          class: 'system',
          isTransient: false,
          authType: 'builtin' },
        { uniqueId: 'org.thingpedia.builtin.thingengine.server',
          name: 'Almond Smart Speaker',
          description: 'Commands that are specific to using Almond as a smart-speaker device.',
          kind: 'org.thingpedia.builtin.thingengine.server',
          version: 0,
          class: 'data',
          ownerTier: 'global',
          isTransient: false,
          authType: 'builtin' },
        { uniqueId: 'org.thingpedia.builtin.thingengine.builtin',
          name: 'Miscellaneous Interfaces',
          description: 'Time, random numbers, and other commands not specific to any skill.',
          kind: 'org.thingpedia.builtin.thingengine.builtin',
          version: 0,
          ownerTier: 'global',
          class: 'data',
          isTransient: true,
          authType: 'builtin' },
        { uniqueId: 'org.thingpedia.builtin.test',
          name: 'Test Device',
          description: 'Test Almond in various ways',
          kind: 'org.thingpedia.builtin.test',
          version: 0,
          ownerTier: 'global',
          class: 'system',
          isTransient: true,
          authType: 'builtin' },
      ]);
  
      const createResult = JSON.parse(await request('/api/devices/create', 'POST', JSON.stringify({
          kind: 'com.xkcd',
      }), { auth, dataContentType: 'application/json' }));
      delete createResult.version;
  
      assert.deepStrictEqual(createResult, {
          uniqueId: 'com.xkcd',
          name: 'XKCD',
          description: 'A webcomic of romance, sarcasm, math, and language.',
          kind: 'com.xkcd',
          ownerTier: 'global',
          class: 'data',
          isTransient: false,
          authType: 'none'
      });
  
      const listResult2 = JSON.parse(await request('/api/devices/list', 'GET', null, { auth }));
      listResult2[listResult2.length-1].version = 0;
      assert.deepStrictEqual(listResult2, [
        { uniqueId: 'thingengine-own-server:81e0e8abba27202a',
          name: 'Almond server (:81e0e8abba27202a)',
          description: 'This is one of your own Almond apps.',
          kind: 'org.thingpedia.builtin.thingengine',
          version: 0,
          ownerTier: 'server',
          class: 'system',
          isTransient: false,
          authType: 'builtin' },
        { uniqueId: 'org.thingpedia.builtin.thingengine.server',
          name: 'Almond Smart Speaker',
          description: 'Commands that are specific to using Almond as a smart-speaker device.',
          kind: 'org.thingpedia.builtin.thingengine.server',
          version: 0,
          class: 'data',
          ownerTier: 'global',
          isTransient: false,
          authType: 'builtin' },
        { uniqueId: 'org.thingpedia.builtin.thingengine.builtin',
          name: 'Miscellaneous Interfaces',
          description: 'Time, random numbers, and other commands not specific to any skill.',
          kind: 'org.thingpedia.builtin.thingengine.builtin',
          version: 0,
          ownerTier: 'global',
          class: 'data',
          isTransient: true,
          authType: 'builtin' },
        { uniqueId: 'org.thingpedia.builtin.test',
          name: 'Test Device',
          description: 'Test Almond in various ways',
          kind: 'org.thingpedia.builtin.test',
          version: 0,
          ownerTier: 'global',
          class: 'system',
          isTransient: true,
          authType: 'builtin' },
        { uniqueId: 'com.xkcd',
          name: 'XKCD',
          description: 'A webcomic of romance, sarcasm, math, and language.',
          kind: 'com.xkcd',
          version: 0,
          ownerTier: 'global',
          class: 'data',
          isTransient: false,
          authType: 'none' }
      ]);
  }
  
  
  async function testMyApiConverse(auth) {
      // ignore the first conversation result as that will show the welcome message
      JSON.parse(await request('/api/converse', 'POST', JSON.stringify({
          command: {
              type: 'command',
              text: 'hello',
          },
      }), { auth, dataContentType: 'application/json' }));
  
      const result1 = JSON.parse(await request('/api/converse', 'POST', JSON.stringify({
          command: {
              type: 'tt',
              code: 'now => @org.thingpedia.builtin.test.dup_data(data_in="foo") => notify;',
          }
      }), { auth, dataContentType: 'application/json' }));
      delete result1.messages[1].uniqueId;
      assert.deepStrictEqual(result1, {
          askSpecial: null,
          messages: [{
              id: 2,
              type: 'command',
              command: '\\t now => @org.thingpedia.builtin.test.dup_data(data_in="foo") => notify;',
          }, {
              id: 3,
              type: 'new-program',
              code: '@org.thingpedia.builtin.test.dup_data(data_in="foo");',
              errors: [],
              icon: 'org.thingpedia.builtin.test',
              name: 'Test',
              results: [{
                  data_in: 'foo',
                  data_out: 'foofoo'
              }],
          }, {
              id: 4,
              type: 'text',
              text: 'The answer is foofoo.',
              icon: 'org.thingpedia.builtin.test'
          }]
      });
  
      const result2 = JSON.parse(await request('/api/converse', 'POST', JSON.stringify({
          command: {
              type: 'command',
              text: 'yes',
          },
      }), { auth, dataContentType: 'application/json' }));
      assert.deepStrictEqual(result2, {
          askSpecial: null,
          messages: [{
              id: 5,
              type: 'command',
              command: 'yes',
          }, {
              id: 6,
              type: 'text',
              text: 'Sorry, I did not understand that. Can you rephrase it?',
              icon: 'org.thingpedia.builtin.test'
          }],
      });
  }
  
  async function testMyApiOAuth(accessToken) {
      const auth = 'Bearer ' + accessToken;
  
      // /profile
      await testMyApiCreateGetApp(auth);
      const uniqueId = await testMyApiCreateWhenApp(auth);
      await testMyApiListApps(auth, uniqueId);
      await testMyApiDeleteApp(auth, uniqueId);
      await testMyApiDevices(auth);
      await testMyApiInvalid(auth);
      await testMyApiConverse(auth);
  }
  
  async function main() {
      const bob = await login();
  
      // user (web almond) api
      const token = await getAccessToken(bob);
      await testMyApiOAuth(token);
  }
  module.exports = main;
  if (!module.parent)
      main();
  