UNPKG

@juzi/wechaty

Version:

Wechaty is a RPA SDK for Chatbot Makers.

387 lines 14.6 kB
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm /** * Wechaty Chatbot SDK - https://github.com/wechaty/wechaty * * @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and * Wechaty Contributors <https://github.com/wechaty>. * * 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. * */ import { test, sinon, } from 'tstest'; import { MemoryCard } from 'memory-card'; import { PuppetMock } from '@juzi/wechaty-puppet-mock'; /** * Huan(202111): must import `./wechaty-impl.js` * before import `./wechaty-base.js` * * Or will throw error: * * ReferenceError: Cannot access 'WechatyBase' before initialization * at file:///home/huan/git/wechaty/wechaty/src/wechaty/wechaty-impl.ts:25:47 * * TODO: find out why */ import './wechaty-impl.js'; import { WechatyBase, } from './wechaty-base.js'; import { WechatySkeleton } from './wechaty-skeleton.js'; import { WechatyBuilder } from '../mods/mod.js'; class WechatyTest extends WechatyBase { } test('static VERSION', async (t) => { t.ok('VERSION' in WechatyBase, 'Wechaty should has a static VERSION property'); }); test('event:start/stop', async (t) => { const wechaty = new WechatyBase({ puppet: '@juzi/wechaty-puppet-mock' }); const startSpy = sinon.spy(); const stopSpy = sinon.spy(); wechaty.on('start', startSpy); wechaty.on('stop', stopSpy); await wechaty.start(); await wechaty.stop(); // console.log(startSpy.callCount) t.ok(startSpy.calledOnce, 'should get event:start once'); t.ok(stopSpy.calledOnce, 'should get event:stop once'); }); // // FIXME: restore this unit test !!! // // test.only('event:scan', async t => { // const m = {} as any // const asyncHook = asyncHooks.createHook({ // init(asyncId: number, type: string, triggerAsyncId: number, resource: Object) { // m[asyncId] = type // }, // before(asyncId) { // // delete m[asyncId] // }, // after(asyncId) { // // delete m[asyncId] // }, // destroy(asyncId) { // delete m[asyncId] // }, // }) // asyncHook.enable() // const wechaty = Wechaty.instance() // const spy = sinon.spy() // wechaty.on('scan', spy) // const scanFuture = new Promise(resolve => wechaty.once('scan', resolve)) // // wechaty.once('scan', () => console.log('FAINT')) // await wechaty.start() // await scanFuture // // await new Promise(r => setTimeout(r, 1000)) // await wechaty.stop() // t.ok(spy.calledOnce, 'should get event:scan') // asyncHook.disable() // console.log(m) // }) test.skip('SKIP DEALING WITH THE LISTENER EXCEPTIONS. on(event, Function)', async (t) => { const spy = sinon.spy(); const wechaty = new WechatyBase(); const EXPECTED_ERROR = new Error('testing123'); wechaty.on('message', () => { throw EXPECTED_ERROR; }); // wechaty.on('scan', () => 42) wechaty.on('error', spy); const messageFuture = new Promise(resolve => wechaty.once('message', resolve)); wechaty.emit('message', {}); await messageFuture; await wechaty.stop(); t.ok(spy.calledOnce, 'should get event:error once'); t.equal(spy.firstCall.args[0], EXPECTED_ERROR, 'should get error from message listener'); }); test.skip('SKIP DEALING WITH THE LISTENER EXCEPTIONS. test async error', async (t) => { // Do not modify the global Wechaty instance class MyWechatyTest extends WechatyBase { } const EXPECTED_ERROR = new Error('test'); const bot = new MyWechatyTest({ puppet: new PuppetMock(), }); const asyncErrorFunction = function () { return new Promise((resolve, reject) => { setTimeout(function () { reject(EXPECTED_ERROR); }, 100); // tslint ask resolve must be called, // so write a falsy value, so that it never called if (+new Date() < 0) { resolve(); } }); }; bot.on('message', async () => { await asyncErrorFunction(); }); bot.on('error', (e) => { t.ok(e.message === EXPECTED_ERROR.message); }); bot.emit('message', {}); await bot.stop(); }); test('use plugin', async (t) => { // Do not modify the gloabl Wechaty instance class MyWechatyTest extends WechatyBase { } let result = ''; // const myGlobalPlugin = function () { // return function (bot: WechatyInterface) { // bot.on('message', () => { result += 'FROM_GLOBAL_PLUGIN:' }) // } // } const myPlugin = function () { return function (bot) { bot.on('message', () => { result += 'FROM_MY_PLUGIN:'; }); }; }; // MyWechatyTest.use(myGlobalPlugin()) const bot = new MyWechatyTest({ puppet: new PuppetMock(), }); bot.use(myPlugin()); await bot.start(); bot.on('message', () => (result += 'FROM_BOT')); bot.emit('message', {}); await bot.stop(); t.equal(result, 'FROM_MY_PLUGIN:FROM_BOT', 'should get plugin works'); }); test('wechatifyUserModules init()', async (t) => { const wechatyTest = new WechatyTest(); t.doesNotThrow(() => wechatyTest.init(), 'should not throw for the 1st time init'); t.doesNotThrow(() => wechatyTest.init(), 'should not throw for the 2nd time init (silence skip)'); }); // TODO: add test for event args test('Perfect restart', async (t) => { const wechaty = new WechatyBase({ puppet: new PuppetMock(), }); try { for (let i = 0; i < 3; i++) { await wechaty.start(); await wechaty.stop(); t.pass('start/stop-ed at #' + i); } t.pass('Wechaty start/restart successed.'); } catch (e) { t.fail(e); } }); test('@event ready', async (t) => { const puppet = new PuppetMock(); const wechaty = new WechatyBase({ puppet }); const sandbox = sinon.createSandbox(); const spy = sandbox.spy(); const mockContact = puppet.mocker.createContact({ name: 'any' }); wechaty.on('ready', spy); t.ok(spy.notCalled, 'should no ready event with new wechaty instance'); await wechaty.start(); t.ok(spy.notCalled, 'should no ready event right start wechaty started'); await puppet.mocker.login(mockContact); puppet.emit('ready', { data: 'test' }); await new Promise(resolve => { setTimeout(resolve, 16 * 1000); }); t.ok(spy.called, 'should fire ready event after puppet ready'); await wechaty.stop(); await wechaty.start(); t.ok(spy.called, 'should fire ready event second time after stop/start wechaty'); await puppet.mocker.login(mockContact); puppet.emit('ready', { data: 'test' }); await new Promise(resolve => { setTimeout(resolve, 16 * 1000); }); t.ok(spy.called, 'should fire ready event third time after stop/start wechaty'); await wechaty.stop(); }); test('ready()', async (t) => { const puppet = new PuppetMock(); const wechaty = new WechatyBase({ puppet }); const sandbox = sinon.createSandbox(); const spy = sandbox.spy(); const mockContact = puppet.mocker.createContact({ name: 'any' }); wechaty.ready() .then(spy) .catch(e => t.fail('rejection: ' + e)); t.ok(spy.notCalled, 'should not ready with new wechaty instance'); await wechaty.start(); t.ok(spy.notCalled, 'should not ready after right start wechaty'); await puppet.mocker.login(mockContact); puppet.emit('ready', { data: 'test' }); await new Promise(resolve => { setTimeout(resolve, 16 * 1000); }); t.ok(spy.calledOnce, 'should ready after puppet ready'); await wechaty.stop(); await wechaty.start(); wechaty.ready() .then(spy) .catch(e => t.fail('rejection: ' + e)); await puppet.mocker.login(mockContact); puppet.emit('ready', { data: 'test' }); await new Promise(resolve => { setTimeout(resolve, 16 * 1000); }); t.ok(spy.calledTwice, 'should ready again after stop/start wechaty'); await wechaty.stop(); }); test('on/off event listener management', async (t) => { const puppet = new PuppetMock(); const wechaty = new WechatyBase({ puppet }); const onMessage = (_) => { }; t.equal(wechaty.listenerCount('message'), 0, 'should no listener after initializing'); wechaty.on('message', onMessage); t.equal(wechaty.listenerCount('message'), 1, 'should +1 listener after on(message)'); wechaty.off('message', onMessage); t.equal(wechaty.listenerCount('message'), 0, 'should -1 listener after off(message)'); }); test('wrapAsync() async function', async (t) => { const puppet = new PuppetMock(); const wechaty = new WechatyBase({ puppet }); const spy = sinon.spy(); wechaty.on('error', spy); const DATA = 'test'; const asyncFunc = async () => DATA; const syncFunc = wechaty.wrapAsync(asyncFunc); t.notOk(syncFunc(), 'should get sync function return void'); t.ok(spy.notCalled, 'should not emit error when sync function return value'); const asyncFunc2 = async () => { throw new Error('test'); }; const syncFunc2 = wechaty.wrapAsync(asyncFunc2); t.doesNotThrow(() => syncFunc2(), 'should not throw when async function throw error'); await wechaty.sleep(0); // wait async event loop task to be executed t.ok(spy.calledOnce, 'should emit error when async function throw error'); }); test('wrapAsync() promise', async (t) => { const puppet = new PuppetMock(); const wechaty = new WechatyBase({ puppet }); const spy = sinon.spy(); wechaty.on('error', spy); const DATA = 'test'; const promise = Promise.resolve(DATA); const wrappedPromise = wechaty.wrapAsync(promise); t.equal(await wrappedPromise, undefined, 'should resolve Promise<any> to void'); const rejection = Promise.reject(new Error('test')); const wrappedRejection = wechaty.wrapAsync(rejection); t.equal(wrappedRejection, undefined, 'should be void and not to reject'); t.equal(spy.callCount, 0, 'should have no error before sleep'); await wechaty.sleep(0); // wait async event loop task to be executed t.equal(spy.callCount, 1, 'should emit error when promise reject with error'); }); test('WechatyBaseProtectedProperty', async (t) => { const noOneLeft = true; t.ok(noOneLeft, 'should match Wechaty properties for every protected property'); }); test('WechatySkeleton: super.{start,stop}()', async (t) => { const sandbox = sinon.createSandbox(); const puppet = new PuppetMock(); const memory = new MemoryCard(); const wechaty = new WechatyTest({ memory, puppet, }); const startStub = sandbox.stub(WechatySkeleton.prototype, 'start').resolves(); const stopStub = sandbox.stub(WechatySkeleton.prototype, 'stop').resolves(); t.ok(startStub.notCalled, 'should not called before start'); t.ok(stopStub.notCalled, 'should not called before stop'); await wechaty.start(); t.ok(startStub.calledOnce, 'should call the skeleton start(), which means all mixin start()s are chained correctly'); t.ok(stopStub.notCalled, 'should not call stop yet'); await wechaty.stop(); t.ok(startStub.calledOnce, 'should only call start once'); t.ok(stopStub.calledOnce, 'should call the skeleton stop(), which means all mixin stops()s are chained correctly'); }); test('ReadyWhenLoggedIn', async (t) => { const puppet = new PuppetMock(); const wechaty = WechatyBuilder.build({ puppet }); const mockContact = puppet.mocker.createContact({ name: 'any' }); await wechaty.start(); let loginCalled = false; wechaty.on('login', () => { loginCalled = true; }); const future = new Promise(resolve => { wechaty.on('ready', () => { if (loginCalled) { t.pass('ready emitted after login'); } else { t.fail('ready emitted before login'); } resolve(); }); }); await puppet.mocker.login(mockContact); await new Promise(resolve => { setTimeout(resolve, 5 * 1000); }); puppet.emit('ready'); await future; await wechaty.stop(); }); test('ReadyDelay', async (t) => { const puppet = new PuppetMock(); const wechaty = WechatyBuilder.build({ puppet }); const mockContact = puppet.mocker.createContact({ name: 'any' }); await wechaty.start(); let loginCalled = false; wechaty.on('login', () => { loginCalled = true; }); const future = new Promise(resolve => { wechaty.on('ready', () => { if (loginCalled) { t.pass('ready emitted after login'); } else { t.fail('ready emitted before login'); } resolve(); }); }); puppet.emit('ready'); await puppet.mocker.login(mockContact); await future; await wechaty.stop(); }); test('ReadyMeetsLogout', async (t) => { const puppet = new PuppetMock(); const wechaty = WechatyBuilder.build({ puppet }); const mockContact = puppet.mocker.createContact({ name: 'any' }); await wechaty.start(); await puppet.mocker.login(mockContact); let readyEmitted = false; wechaty.on('ready', () => { readyEmitted = true; }); puppet.emit('ready'); await new Promise(resolve => { setTimeout(resolve, 5 * 1000); }); puppet.emit('logout', { contactId: mockContact.id }); await new Promise(resolve => { setTimeout(resolve, 15 * 1000); }); t.ok(!readyEmitted, 'ready should not be emitted because puppet logout'); puppet.emit('login', { contactId: mockContact.id }); await new Promise(resolve => { setTimeout(resolve, 5 * 1000); }); t.ok(!readyEmitted, 'ready should not be emitted because it should wait for a new ready'); puppet.emit('ready'); await new Promise(resolve => { setTimeout(resolve, 16 * 1000); }); t.ok(readyEmitted, 'ready should be emitted'); await wechaty.stop(); }); //# sourceMappingURL=wechaty-base.spec.js.map