提交 6f574501 编写于 作者: Huan (李卓桓)'s avatar Huan (李卓桓)

unit tests by tape

上级 457a79f9
......@@ -8,3 +8,5 @@ If you are interested in wechaty, please come back 4 weeks later. ;-p
https://news.ycombinator.com/item?id=5388444
https://github.com/KostyaKow/shell_matrix
http://hackertyper.com/
code check use eslint
......@@ -20,8 +20,13 @@
*/
;(function (port) {
var port = port || 8788
port = port || 8788
/**
*
* Get All I Need from Browser
*
*/
var injector = angular.element(document).injector()
var rootScope = injector.get("$rootScope")
var http = injector.get("$http")
......@@ -32,42 +37,85 @@
var loginScope = angular.element(".login_box").scope()
var Wechaty = function () {
debug(true)
hook()
initZlog()
zlog('wechaty port ' + port)
/**
* socket might be destroyed(reconnected)
*/
var socket
initSocket()
hookUnload()
hookMessage()
zlog('Wechaty injected!. ;-D')
}
angular.extend(Wechaty, {
debug: debug
, getLoginStatusCode: function () { return loginScope.code }
getLoginStatusCode: function () { return loginScope.code }
, getLoginQrImgUrl: function () { return loginScope.qrcodeUrl }
, isLogined: function () { return 200===loginScope.code }
, isReady: function () { return !!(angular && angular.element && angular.element("body")) }
, log: function (msg) { SOCKET && SOCKET.emit('log', msg) }
, ping: function () { return 'pong' }
})
function debug(enable) {
function initZlog() {
var enable = true
if (enable) {
delete window.console.log
delete window.console
window.console.zlog = window.console.log
window.zlog = function (s) { return window.console.zlog(s) }
window.console.log = function () {}
if (console)
delete console.log
delete console
console.zlog = window.console.log
window.zlog = function (s) { return console.zlog(s) }
console.log = function () {}
} else {
window.zlog = function () {}
}
}
function hook() {
function hookMessage() {
rootScope.$on("message:add:success", function (event, data) {
http.post('https://localhost:' + port, data)
socket.emit('message', data)
.catch(function (e) { zlog('socket.emit(message, data) fail:'); zlog(e) })
})
}
function hookUnload() {
window.addEventListener ('unload', function (e) {
socket.emit('unload')
})
}
function initSocket() {
// Wechaty global variable: socket
socket = io.connect('https://127.0.0.1:' + port)
zlog('socket: ' + socket)
socket.on('connect', function() {
zlog('on connect entried')
rootScope.$on("message:add:success", function (event, data) {
socket.emit('message', data)
})
socket.on('disconnect', function(e) {
zlog('event: socket disconnect')
socket.emit('disconnect', e)
// Reconnect...
setTimeout(initSocket, 1000)
})
})
}
window.Wechaty = Wechaty
return 'Wechaty'
return Wechaty
}(arguments[0]))
......@@ -11,28 +11,36 @@
*/
/**
/**************************************
*
* Class PuppetWeb
*
*/
***************************************/
const EventEmitter = require('events')
class PuppetWeb extends EventEmitter {
constructor() {
constructor(port) {
super()
const PORT = 8788 // W(87) X(88), ascii char code ;-]
const server = this.server = new WebServer(PORT)
const EVENTS = [
this.port = port || PORT
const server = this.server = new WebServer(this.port)
const EVENTS_IN = [
'message'
, 'login'
, 'logout'
]
EVENTS_IN.map( event =>
server.on(event, data => this.emit(event, data) )
)
EVENTS.map( event => {
server.on(event, data => { this.emit(event, data) })
})
const EVENTS_OUT = [
'sent'
]
EVENTS_OUT.map( event =>
this.on(event, data => server.emit(event, data) )
)
}
/**
......@@ -40,8 +48,12 @@ class PuppetWeb extends EventEmitter {
* Interface Methods
*
*/
alive() {
return this.server && this.server.isLogined()
alive() { return this.server && this.server.isLogined() }
destroy() {
if (this.server) {
this.server.quit()
delete this.server
}
}
/**
......@@ -49,19 +61,22 @@ class PuppetWeb extends EventEmitter {
* Public Methods
*
*/
getLoginQrImgUrl() {
return this.server.browserProxy('Wechaty.getLoginQrImgUrl()')
}
getLoginQrImgUrl() { return this.server.Wechaty_getLoginQrImgUrl() }
getLoginStatusCode() { return this.server.Wechaty_getLoginStatusCode() }
}
/**
/****************************************
*
* Class WebServer
*
*/
*
*
*
*
*
*
***************************************/
const fs = require('fs')
const io = require('socket.io')
......@@ -71,30 +86,50 @@ const bodyParser = require('body-parser')
const Express = require('express')
class WebServer extends EventEmitter {
constructor(port) {
constructor() {
super()
/**
*
* io events proxy between server & browser
*
*/
this.WEBSERVER_EVENTS = [
this.EVENTS_IN = [
'message'
, 'login'
, 'logout'
, 'unload'
]
this.EVENTS_OUT = [
'send'
, 'logout'
]
const express = this.initExpress()
const server = this.initHttpsServer(express, port)
const socketio = this.initSocketIo(server)
this.logined = false
const browser = this.browser = new WebBrowser(port)
this.on('login' , () => this.logined = true )
this.on('logout', () => this.logined = false )
}
this.online = false
init(port) {
this.port = port || 8788
this.express = this.initExpress()
this.server = this.initHttpsServer(this.express, this.port)
this.socketio = this.initSocketIo(this.server)
this.browser = new WebBrowser()
this.browser.init(this.port)
.then(() => {
console.error('browser init finished with port: ' + this.port + '.')
this.on('unload', () => {
console.error('webserver received unload event')
this.browser.inject()
.then(() => console.error('re-injected'))
})
})
this.on('login' , () => { this.online = true })
this.on('logout', () => { this.online = false })
// this.debugLoop()
}
/**
......@@ -103,14 +138,15 @@ class WebServer extends EventEmitter {
*
*/
initHttpsServer(express, port) {
port = port || this.port
// http://blog.mgechev.com/2014/02/19/create-https-tls-ssl-application-with-express-nodejs/
// openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365
// openssl rsa -in key.pem -out newkey.pem && mv newkey.pem key.pem
return https.createServer({
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
}, express).listen(port, function () {
console.log('Example app listening on port ' + port + '!')
key: fs.readFileSync (path.join(path.dirname(__filename), 'key.pem')),
cert: fs.readFileSync(path.join(path.dirname(__filename), 'cert.pem'))
}, express).listen(port, () => {
console.error(`initHttpsServer listening on port ${port}!`)
})
}
......@@ -129,16 +165,16 @@ class WebServer extends EventEmitter {
next()
})
app.get('/', function (req, res) {
console.log(new Date())
res.send('Hello World!')
app.get('/ping', function (req, res) {
console.error(new Date() + ' GET /ping')
res.send('pong')
})
app.post('/', function (req, res) {
console.log('post ' + new Date())
console.log(req.body)
res.send(req.body)
})
// app.post('/', function (req, res) {
// console.log('post ' + new Date())
// console.log(req.body)
// res.send(req.body)
// })
return app
}
......@@ -153,24 +189,33 @@ class WebServer extends EventEmitter {
log: true
})
ioServer.sockets.on('connection', function(sock) {
ioServer.sockets.on('connection', (s) => {
console.log('socket.on connection entried')
socket = sock
let socket = s
socket.on('disconnect', function() {
console.error('socket.io disconnected')
/**
* Possible conditions:
* 1. Browser reload
* 2. Lost connection(Bad network
* 3.
*/
socket = null
})
this.WEBSERVER_EVENTS.map(event => {
// Events <--from-- Wechaty@Broswer
this.EVENTS_IN.map(event => {
// Events from Wechaty@Broswer --to--> Server
socket.on(event, data => {
console.log(`recv even ${event} from browser`)
console.log(`recv event[${event}] from browser`)
this.emit(event, data)
})
})
// Event --to--> Wechaty@Browser
this.EVENTS_OUT.map(event => {
// Event from Server --to--> Wechaty@Browser
this.on(event, data => {
console.log(`sent even ${event} to browser`)
console.log(`sent even[${event}] to browser`)
socket.emit(event, data)
})
})
......@@ -188,13 +233,50 @@ class WebServer extends EventEmitter {
}
isLogined() {
return this.online
return this.logined
}
browserProxy(wechatyFunction) {
if (!this.browser) throw new Error('theres no broswer found in server instance!')
quit() {
if (this.browser) {
this.browser.quit()
delete this.browser
}
if (this.server) {
// TODO: close server
console.log('todo: close & quite server')
}
}
/**
*
* Proxy Call to Wechaty in Browser
*
*/
browserExecute(script) {
if (!this.browser)
throw new Error('no browser!')
return this.browser.execute(script)
}
return this.browser.executeScript(wechatyFunction)
proxyWechaty(wechatyFunc) {
const args = Array.prototype.slice.call(arguments, 1)
const argsJson = JSON.stringify(args)
const wechatyScript = `return (Wechaty && Wechaty.${wechatyFunc}.apply(undefined, JSON.parse('${argsJson}')))`
console.error('proxyWechaty: ' + wechatyScript)
return this.browserExecute(wechatyScript)
}
Wechaty_getLoginStatusCode() { return this.proxyWechaty('getLoginStatusCode') }
Wechaty_getLoginQrImgUrl() { return this.proxyWechaty('getLoginQrImgUrl') }
// Wechaty_CARPEDIEM() { return this.proxyWechaty('call') }
debugLoop() {
this.Wechaty_getLoginStatusCode().then((c) => {
console.error(`login status code: ${c}`)
setTimeout(this.debugLoop.bind(this), 3000)
})
}
}
......@@ -203,87 +285,71 @@ class WebServer extends EventEmitter {
*/
/**
/****************************************
*
* Class WebBrowser
*
*/
***************************************/
const path = require('path')
const WebDriver = require('selenium-webdriver')
class WebBrowser {
constructor(port) {
const BROWSER = this.BROWSER = 'chrome'
const PORT = this.PORT = port || 8788
const driver = this.driver = new WebDriver.Builder().forBrowser(BROWSER).build()
constructor(browser) {
const BROWSER = this.BROWSER = browser || 'chrome'
this.open()
.then(this.inject.bind(this))
const driver = this.driver = new WebDriver.Builder().forBrowser(BROWSER).build()
}
this.loop()
init(port) {
console.log(`browser inititializing... port: ${port}`)
this.port = port || 8788
return this.open().then(this.inject.bind(this))
}
open() {
const WX_URL = 'https://wx.qq.com'
this.driver.get(WX_URL) // open wechat web page
console.log('waitting for dom ready')
return this.driver.get(WX_URL) // open wechat web page
return this.driver.wait(() => {
return this.driver.isElementPresent(WebDriver.By.css('div.login_box'))
}, 60*1000, '\nFailed to wait div.login_box')
// return this.driver.wait(() => {
// return this.driver.isElementPresent(WebDriver.By.css('div.login_box'))
// }, 60*1000, '\nFailed to wait div.login_box')
}
inject() {
const injectio = fs.readFileSync(
path.join(__dirname, 'puppet-web-injectio.js')
path.join(path.dirname(__filename), 'puppet-web-injectio.js')
, 'utf8'
)
console.log('start injecting')
const socketio = fs.readFileSync(
// 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js'
path.join(path.dirname(__filename), '/socket.io.min.js')
, 'utf8'
)
return this.executeScript(injectio, this.PORT)
console.error('injecting')
return this.execute(socketio)
.then(() => this.execute(injectio, this.port))
.then(() => this.execute('Wechaty()'))
.then(() => console.error('injected'))
}
loop() {
console.log('start wait Login')
const that = this
function startCheck() {
that.executeScript('return Wechaty.getLoginStatusCode()').then( function (c) {
console.log('got code: ' + c)
if (200!=c) {
setTimeout(startCheck, 500)
} else {
doLogin()
}
})
}
startCheck()
function doLogin() {
console.log('logined?!')
quit() {
if (this.driver) {
this.driver.quit()
delete this.driver
}
}
quit() {
return this.driver.quit()
}
getLoginQrImgUrl() {
return this.executeScript('return Wechaty.getLoginQrImgUrl()')
}
executeScript(script) {
execute(script, ...args) {
if (!this.driver)
throw new Error('driver not found')
// a promise
return this.driver
.executeScript(script)
return this.driver.executeScript(script, args)
}
}
PuppetWeb.WebServer = WebServer
PuppetWeb.WebBrowser = WebBrowser
module.exports = PuppetWeb
......@@ -16,6 +16,7 @@ class Puppet extends EventEmitter {
this.soul = soul
this.soul.on('message', data => { this.emit('message', data) })
}
detach() {
if (!this.soul) throw new Error('there is no soul inside to detach!')
this.soul.destroy()
......
......@@ -29,6 +29,14 @@
"selenium-webdriver": "",
"socket.io": "^1.4.5"
},
"eslintConfig": {
"env": {
"browser": true,
"node": true,
"es6": true,
"shared-node-browser": true
}
},
"devDependencies": {
"tape": "^4.5.1"
}
......
const test = require('tape')
const PuppetWeb = require('../lib/puppet-web')
const WebBrowser = PuppetWeb.WebBrowser
test('WebBrowser class tests', function (t) {
//t.plan(5)
const b = new WebBrowser({browser: 'chrome'})
t.ok(b, 'WebBrowser instance created')
b.open()
.then(() => {
t.ok(true, 'url opened')
b.inject()
.then(() => {
t.ok(true, 'wechaty injected')
b.execute('return 1+1')
.then(n => t.equal(n, 2, 'exec 1+1 in browser, equal 2'))
b.execute('return Wechaty && Wechaty.ping()')
.then(r => t.equal(r, 'pong', 'Wechaty.ping() returns pong'))
b.execute('return Wechaty && Wechaty.isReady()')
.then(r => t.notEqual(typeof r, 'bool', 'Wechaty.isReady() returns bool'))
t.end()
})
})
b.quit()
})
const https = require('https')
const test = require('tape')
const PuppetWeb = require('../lib/puppet-web')
const WebServer = PuppetWeb.WebServer
test('WebServer basic tests', function (t) {
t.plan(9)
const PORT = 58788
const s = new WebServer()
t.equal(typeof s , 'object', 'WebServer instance created')
const express = s.initExpress()
t.equal(typeof express, 'function', 'init express')
delete express
const server = s.initHttpsServer(express, PORT)
t.equal(typeof server, 'object', 'init server')
server.on('close', () => t.ok(true, 'HttpsServer quited'))
server.close(() => t.ok(true, 'HttpsServer closed'))
delete server
const socketio = s.initSocketIo()
t.equal(typeof socketio, 'object', 'init socket io')
delete socketio
t.equal(s.isLogined() , false , 'instance not logined')
s.emit('login')
t.equal(s.isLogined() , true , 'logined after login event')
s.emit('logout')
t.equal(s.isLogined() , false , 'logouted after logout event')
s.quit()
delete s
//t.end()
})
test('WebServer smoking tests', function (t) {
const PORT = 58788
const s = new WebServer()
t.plan(1)
console.log(`s.init(${PORT})`)
s.init(PORT)
const options = require('url').parse(`https://localhost:${PORT}/ping`)
Object.assign(options, {rejectUnauthorized: false}) // permit self-signed CA
https.get(options, (res) => {
res.on('data', chunk => {
t.equal(chunk.toString(), 'pong', 'https get /ping return pong')
})
}).on('error', e => {
console.error(e)
t.ok(false, 'https get fail')
})
})
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册