Many test, many refactoring, only core, lib and runner left to test and fix
continuous-integration/appveyor/branch AppVeyor build succeeded Details

master
Jonatan Nilsson 2022-01-13 17:02:55 +00:00
parent 04747bc1c6
commit 17830d7f8d
7 changed files with 435 additions and 191 deletions

View File

@ -33,6 +33,8 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
h = https
}
let req = null
return new Promise(function(resolve, reject) {
if (!path) {
return reject(new Error('Request path was empty'))
@ -46,8 +48,6 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
}
let timeout = fastRaw ? 5000 : config.timeout || 10000
let req = null
let timedout = false
let timer = setTimeout(function() {
timedout = true
@ -103,7 +103,9 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
req.on('error', function(err) {
if (timedout) return
reject(new Error(`Error during request ${path}: ${err.message}`))
let wrapped = new Error(`Error during request ${path}: ${err.message}`)
wrapped.code = err.code
reject(wrapped)
})
req.on('timeout', function(err) {
if (timedout) return
@ -112,6 +114,9 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
req.end()
}).then(function(res) {
if (req) {
req.destroy()
}
if (!filePath && !fastRaw) {
if (typeof(res.body) === 'string') {
try {

View File

@ -1,143 +1,104 @@
import lowdb from 'lowdb'
import FileAsync from 'lowdb/adapters/FileAsync.js'
import { Low, JSONFile } from 'lowdb'
import { type } from 'os'
let lastId = -1
export default function GetDB(util, log, filename = 'db.json') {
let fullpath = util.getPathFromRoot('./' + filename)
const adapter = new JSONFile(fullpath)
const db = new Low(adapter)
// Take from https://github.com/typicode/lodash-id/blob/master/src/index.js
// from package lodash-id
const lodashId = {
// Empties properties
__empty: function (doc) {
this.forEach(doc, function (value, key) {
delete doc[key]
})
},
db.id = 'id'
// Copies properties from an object to another
__update: function (dest, src) {
this.forEach(src, function (value, key) {
dest[key] = value
})
},
// Removes an item from an array
__remove: function (array, item) {
var index = this.indexOf(array, item)
if (index !== -1) array.splice(index, 1)
},
__id: function () {
var id = this.id || 'id'
return id
},
getById: function (collection, id) {
var self = this
return this.find(collection, function (doc) {
if (self.has(doc, self.__id())) {
return doc[self.__id()] === id
}
})
},
createId: function (collection, doc) {
let next = new Date().getTime()
if (next <= lastId) {
next = lastId + 1
db.createId = function(collection) {
if (collection.length) {
return (collection[collection.length - 1].id || 0) + 1
}
lastId = next
return next
},
insert: function (collection, doc) {
doc[this.__id()] = doc[this.__id()] || this.createId(collection, doc)
var d = this.getById(collection, doc[this.__id()])
if (d) throw new Error('Insert failed, duplicate id')
collection.push(doc)
return doc
},
upsert: function (collection, doc) {
if (doc[this.__id()]) {
// id is set
var d = this.getById(collection, doc[this.__id()])
if (d) {
// replace properties of existing object
this.__empty(d)
this.assign(d, doc)
} else {
// push new object
collection.push(doc)
}
} else {
// create id and push new object
doc[this.__id()] = this.createId(collection, doc)
collection.push(doc)
}
return doc
},
updateById: function (collection, id, attrs) {
var doc = this.getById(collection, id)
if (doc) {
this.assign(doc, attrs, {id: doc.id})
}
return doc
},
updateWhere: function (collection, predicate, attrs) {
var self = this
var docs = this.filter(collection, predicate)
docs.forEach(function (doc) {
self.assign(doc, attrs, {id: doc.id})
})
return docs
},
replaceById: function (collection, id, attrs) {
var doc = this.getById(collection, id)
if (doc) {
var docId = doc.id
this.__empty(doc)
this.assign(doc, attrs, {id: docId})
}
return doc
},
removeById: function (collection, id) {
var doc = this.getById(collection, id)
this.__remove(collection, doc)
return doc
},
removeWhere: function (collection, predicate) {
var self = this
var docs = this.filter(collection, predicate)
docs.forEach(function (doc) {
self.__remove(collection, doc)
})
return docs
return 1
}
}
export default function GetDB(util, log) {
const adapter = new FileAsync(util.getPathFromRoot('./db.json'))
db.get = function(collection, id, returnIndex = false) {
let col = db.getCollection(collection)
for (let i = col.length - 1; i >= 0; i--) {
if (col[i][db.id] === id) {
if (returnIndex) return i
return col[i]
}
}
return null
}
db.getCollection = function(collection) {
if (typeof(collection) === 'string') {
return db.data[collection]
}
return collection
}
db.upsert = function(collection, item) {
let col = db.getCollection(collection)
if (item[db.id]) {
let i = db.get(col, item[db.id], true)
if (i !== null) {
col[i] = item
return
}
}
item[db.id] = db.createId(col)
col.push(item)
}
db.remove = function(collection, itemOrId) {
let col = db.getCollection(collection)
let id = itemOrId
if (typeof(id) === 'object') {
id = id[db.id]
}
for (let i = col.length - 1; i >= 0; i--) {
if (col[i][db.id] === id) {
col.splice(i, 1)
return true
}
}
return false
}
return db.read()
.then(function() {
db.data ||= {
core: {
app: {
active: null,
latestInstalled: null,
latestVersion: null,
versions: [],
},
manager: {
active: null,
latestInstalled: null,
latestVersion: null,
versions: [],
},
version: 1,
}
}
return db.write()
})
.then(
function() { return db },
function(err) {
let wrapped = new Error(`Error writing to ${fullpath}: ${err.code}`)
wrapped.code = err.code
throw wrapped
}
)
/*
const adapter = new FileAsync(util.getPathFromRoot('./' + filename))
return lowdb(adapter)
.then(function(db) {
db._.mixin(lodashId)
db.adapterFilePath = util.getPathFromRoot('./db.json')
db.adapterFilePath = util.getPathFromRoot('./' + filename)
return db.defaults({
core: {
@ -154,8 +115,9 @@ export default function GetDB(util, log) {
})
.write()
.then(
function() { db },
function(e) { log.error(e, 'Error writing defaults to lowdb') }
function() { return db },
function() { throw new Error('Error writing defaults to lowdb'); }
)
})
*/
}

View File

@ -1,73 +1,49 @@
import http from 'http'
import https from 'https'
export default class HttpServer {
constructor(config) {
this.active = {
app: false,
manage: false,
dev: false,
this.ishttps = false
this.active = null
this.sockets = new Set()
this.creator = http
if (config && config.https) {
this.creator = https
this.ishttps = true
}
this.sockets = {
app: new Set(),
manage: new Set(),
dev: new Set(),
}
this._context = 'dev'
}
setContext(name) {
if (name !== 'app' && name !== 'manage' && name !== 'dev') {
throw new Error('Cannot call setContext with values other than app or manage')
}
this._context = name
}
createServer(opts, listener) {
return this._createServer(this._context, opts, listener)
}
_createServer(name, opts, listener) {
let server = http.createServer(opts, listener)
let server = this.creator.createServer(opts, listener)
server.on('connection', (socket) => {
this.sockets[name].add(socket)
this.sockets.add(socket)
socket.once('close', () => {
this.sockets[name].delete(socket)
this.sockets.delete(socket)
})
})
this.active[name] = server
this.active = server
return server
}
getServer(name) {
return this.active[name]
}
closeServer() {
if (!this.active) return Promise.resolve()
async closeServer(name) {
if (!this.active[name]) return false
try {
return await new Promise((res, rej) => {
this.sockets[name].forEach(function(socket) {
socket.destroy()
})
this.sockets[name].clear()
this.active[name].close(function(err) {
if (err) return rej(err)
// Waiting 1 second for it to close down
setTimeout(function() { res(true) }, 1000)
})
return new Promise((res, rej) => {
this.sockets.forEach(function(socket) {
socket.destroy()
})
} catch (err) {
throw new Error(`Error closing ${name}: ${err.message}`)
}
}
this.sockets.clear()
getCurrentServer() {
return this.active[this._context]
this.active.close(err => {
if (err) return rej(err)
this.active = null
// Waiting 1 second for it to close down
setTimeout(function() {res() }, 1000)
})
})
}
}

View File

@ -26,7 +26,7 @@
"dependencies": {
"bunyan-lite": "^1.0.1",
"lodash": "^4.17.20",
"lowdb": "^1.0.0"
"lowdb": "^3.0.0"
},
"devDependencies": {
"eltro": "^1.3.0"

View File

@ -1,2 +1,174 @@
import { Eltro as t, assert} from 'eltro'
import { Eltro as t, assert, stub } from 'eltro'
import fs from 'fs/promises'
import lowdb from '../core/db.mjs'
import Util from '../core/util.mjs'
var util = new Util(import.meta.url)
var logger = {
info: stub(),
error: stub(),
}
t.before(function() {
return fs.rm('./test/db_test.json')
.catch(function() {})
})
t.afterEach(function() {
return fs.rm('./test/db_test.json')
.catch(function() { })
})
t.test('Should auto create file with some defaults', async function() {
await assert.isRejected(fs.stat('./test/db_test.json'))
let db = await lowdb(util, logger, 'db_test.json')
let stat = await fs.stat('./test/db_test.json')
assert.ok(stat.size > 0)
assert.ok(db.data.core)
assert.ok(db.data.core.app)
assert.ok(db.data.core.app.versions)
assert.ok(db.data.core.manager)
assert.ok(db.data.core.manager.versions)
})
t.test('Should support reading from db', async function() {
const assertValue = { a: 1 }
await assert.isRejected(fs.stat('./test/db_test.json'))
let db = await lowdb(util, logger, 'db_test.json')
db.data.test = assertValue
await db.write()
assert.strictEqual(db.data.test, assertValue)
let dbSecondary = await lowdb(util, logger, 'db_test.json')
assert.notStrictEqual(dbSecondary.data.test, assertValue)
assert.deepStrictEqual(dbSecondary.data.test, assertValue)
})
t.test('should throw if unable to write to file', function() {
return assert.isRejected(lowdb(util, logger, '../test'))
})
t.test('should have basic database-like functions defined', async function() {
const assertItem1 = { a: 1 }
const assertItem2 = { a: 2 }
const assertItem3 = { a: 3 }
let db = await lowdb(util, logger, 'db_test.json')
assert.strictEqual(db.id, 'id')
db.data.myarr = []
db.upsert(db.data.myarr, assertItem1)
db.upsert(db.data.myarr, assertItem2)
assert.strictEqual(db.data.myarr.length, 2)
assert.ok(assertItem1.id)
assert.ok(assertItem2.id)
assert.notStrictEqual(assertItem1.id, assertItem2.id)
assert.strictEqual(db.get(db.data.myarr, assertItem1.id), assertItem1)
assert.strictEqual(db.get(db.data.myarr, assertItem2.id), assertItem2)
assert.strictEqual(db.get(db.data.myarr, assertItem2.id + 1), null)
await db.write()
let dbSec = await lowdb(util, logger, 'db_test.json')
assert.strictEqual(dbSec.data.myarr.length, 2)
assert.notStrictEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem1)
assert.notStrictEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), assertItem2)
assert.deepEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem1)
assert.deepEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), assertItem2)
dbSec.upsert(dbSec.data.myarr, assertItem3)
assert.strictEqual(dbSec.data.myarr.length, 3)
assert.ok(assertItem3.id)
assert.strictEqual(dbSec.get(dbSec.data.myarr, assertItem3.id), assertItem3)
assert.strictEqual(assertItem2.id + 1, assertItem3.id)
const assertItem4 = { a: 4, id: assertItem2.id }
dbSec.upsert(dbSec.data.myarr, assertItem4)
assert.strictEqual(dbSec.data.myarr.length, 3)
assert.notDeepEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), assertItem2)
assert.deepEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), assertItem4)
const assertItem5 = { a: 5, id: assertItem1.id }
dbSec.upsert(dbSec.data.myarr, assertItem5)
assert.strictEqual(dbSec.data.myarr.length, 3)
assert.notDeepEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem1)
assert.deepEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem5)
dbSec.remove(dbSec.data.myarr, assertItem2.id)
assert.strictEqual(dbSec.data.myarr.length, 2)
assert.strictEqual(dbSec.get(dbSec.data.myarr, assertItem2.id), null)
dbSec.remove(dbSec.data.myarr, assertItem1.id)
assert.strictEqual(dbSec.data.myarr.length, 1)
assert.strictEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), null)
})
t.test('should have basic database-like functions with string-like name of collection', async function() {
const assertItem1 = { a: 1 }
const assertItem2 = { a: 2 }
const assertItem3 = { a: 3 }
let db = await lowdb(util, logger, 'db_test.json')
assert.strictEqual(db.id, 'id')
db.data.myarr = []
db.upsert('myarr', assertItem1)
db.upsert('myarr', assertItem2)
assert.strictEqual(db.data.myarr.length, 2)
assert.ok(assertItem1.id)
assert.ok(assertItem2.id)
assert.notStrictEqual(assertItem1.id, assertItem2.id)
assert.strictEqual(db.get('myarr', assertItem1.id), assertItem1)
assert.strictEqual(db.get('myarr', assertItem2.id), assertItem2)
assert.strictEqual(db.get('myarr', assertItem2.id + 1), null)
await db.write()
let dbSec = await lowdb(util, logger, 'db_test.json')
assert.strictEqual(dbSec.data.myarr.length, 2)
assert.notStrictEqual(dbSec.get('myarr', assertItem1.id), assertItem1)
assert.notStrictEqual(dbSec.get('myarr', assertItem2.id), assertItem2)
assert.deepEqual(dbSec.get('myarr', assertItem1.id), assertItem1)
assert.deepEqual(dbSec.get('myarr', assertItem2.id), assertItem2)
dbSec.upsert('myarr', assertItem3)
assert.strictEqual(dbSec.data.myarr.length, 3)
assert.ok(assertItem3.id)
assert.strictEqual(dbSec.get('myarr', assertItem3.id), assertItem3)
assert.strictEqual(assertItem2.id + 1, assertItem3.id)
const assertItem4 = { a: 4, id: assertItem2.id }
dbSec.upsert('myarr', assertItem4)
assert.strictEqual(dbSec.data.myarr.length, 3)
assert.notDeepEqual(dbSec.get('myarr', assertItem2.id), assertItem2)
assert.deepEqual(dbSec.get('myarr', assertItem2.id), assertItem4)
const assertItem5 = { a: 5, id: assertItem1.id }
dbSec.upsert('myarr', assertItem5)
assert.strictEqual(dbSec.data.myarr.length, 3)
assert.notDeepEqual(dbSec.get('myarr', assertItem1.id), assertItem1)
assert.deepEqual(dbSec.get('myarr', assertItem1.id), assertItem5)
dbSec.remove('myarr', assertItem2.id)
assert.strictEqual(dbSec.data.myarr.length, 2)
assert.strictEqual(dbSec.get('myarr', assertItem2.id), null)
dbSec.remove('myarr', assertItem1.id)
assert.strictEqual(dbSec.data.myarr.length, 1)
assert.strictEqual(dbSec.get('myarr', assertItem1.id), null)
})

133
test/http.test.mjs Normal file
View File

@ -0,0 +1,133 @@
import { Eltro as t, assert, stub } from 'eltro'
import http from 'http'
import https from 'https'
import { request } from '../core/client.mjs'
import HttpServer from '../core/http.mjs'
const port = 61413
let prefix = `http://localhost:${port}/`
t.describe('config', function() {
t.test('should use https if https is true', function() {
let server = new HttpServer()
assert.strictEqual(server.creator, http)
assert.strictEqual(server.ishttps, false)
server = new HttpServer({ https: true })
assert.strictEqual(server.creator, https)
assert.strictEqual(server.ishttps, true)
})
})
t.describe('Sockets', function() {
let http = new HttpServer()
t.after(function() {
http.closeServer().then(function() { }, function(err) {
console.error(err)
})
})
t.test('should keep track of sockets through its lifetime', function(cb) {
let actives = []
let server = http.createServer(function(req, res) {
req.on('error', function(err) { cb(err) })
res.on('error', function(err) { cb(err) })
res.on('finish', function() { })
actives.push(res)
})
Promise.resolve()
.then(async function() {
await new Promise(function(res, rej) {
server.listen(port, function(err) { if (err) rej(err); res()})
})
assert.strictEqual(actives.length, 0)
assert.strictEqual(http.sockets.size, 0)
request({}, prefix).then(function() {}, cb)
request({}, prefix).then(async function() {
while (http.sockets.size > 0) {
await new Promise(function(res) { setTimeout(res, 10) })
}
assert.strictEqual(http.sockets.size, 0)
cb()
}, cb)
while (actives.length < 2) {
await new Promise(function(res) { setTimeout(res, 10) })
}
assert.strictEqual(http.sockets.size, 2)
actives[0].statusCode = 200
actives[0].end('{}')
actives[1].statusCode = 200
actives[1].end('{}')
}).catch(cb)
})
})
t.describe('Close', function() {
let http = new HttpServer()
t.after(function() {
http.closeServer().then(function() { }, function(err) {
console.error(err)
})
})
t.test('should support forcefully closing them on server close', function(cb) {
let requestErrors = []
let serverErrors = []
let server = http.createServer(function(req, res) {
req.on('error', function(err) { serverErrors.push(err) })
res.on('error', function(err) { serverErrors.push(err) })
res.on('finish', function() { })
})
Promise.resolve()
.then(async function() {
await new Promise(function(res, rej) {
server.listen(port, function(err) { if (err) rej(err); res()})
})
assert.strictEqual(http.sockets.size, 0)
request({}, prefix).then(
function() { cb(new Error('first succeeded')) },
function(err) { requestErrors.push(err) }
)
request({}, prefix).then(
function() { cb(new Error('first succeeded')) },
function(err) { requestErrors.push(err) }
)
while (http.sockets.size < 2) {
await new Promise(function(res) { setTimeout(res, 10) })
}
http.closeServer().then(function() { }, cb)
while (requestErrors.length < 2) {
await new Promise(function(res) { setTimeout(res, 10) })
}
assert.strictEqual(http.sockets.size, 0)
assert.strictEqual(requestErrors.length, 2)
assert.strictEqual(serverErrors.length, 2)
assert.strictEqual(serverErrors[0].code, 'ECONNRESET')
assert.strictEqual(serverErrors[1].code, 'ECONNRESET')
assert.strictEqual(requestErrors[0].code, 'ECONNRESET')
assert.strictEqual(requestErrors[1].code, 'ECONNRESET')
while (requestErrors.length < 2) {
await new Promise(function(res) { setTimeout(res, 10) })
}
while (http.active) {
await new Promise(function(res) { setTimeout(res, 10) })
}
})
.then(function() { cb()}, cb)
})
})

View File

@ -29,10 +29,6 @@ t.describe('#getPathFromRoot()', function() {
t.describe('#getUrlFromRoot()', function() {
t.test('should return an import compatible path', async function() {
var util = new Util(import.meta.url)
let err = await assert.isRejected(import(util.getPathFromRoot('template.mjs')))
assert.match(err.message, /ESM/i)
assert.match(err.message, /file/i)
assert.match(err.message, /data/i)
let data = await import(util.getUrlFromRoot('template.mjs'))
assert.deepEqual(data.default, { a: 1 })