Lots more development, git provider finished implementing. Started on creating application
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded

This commit is contained in:
Jonatan Nilsson 2022-01-21 02:43:48 +00:00
parent 0948885671
commit 726ac81eb3
12 changed files with 771 additions and 37 deletions

5
core/application.mjs Normal file
View file

@ -0,0 +1,5 @@
export default class Application extends EventEmitter {
constructor() {
}
}

View file

@ -1,7 +1,7 @@
import http from 'http' import http from 'http'
import https from 'https' import https from 'https'
import stream from 'stream/promises'
import fs from 'fs' import fs from 'fs'
import url from 'url'
function resolveRelative(from, to) { function resolveRelative(from, to) {
const resolvedUrl = new URL(to, new URL(from, 'resolve://')); const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
@ -23,7 +23,7 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
} }
let parsed let parsed
try { try {
parsed = new url.URL(path) parsed = new URL(path)
} catch { } catch {
return Promise.reject(new Error('URL was empty or invalid')) return Promise.reject(new Error('URL was empty or invalid'))
} }
@ -43,8 +43,8 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
'User-Agent': 'TheThing/service-core', 'User-Agent': 'TheThing/service-core',
Accept: 'application/vnd.github.v3+json' Accept: 'application/vnd.github.v3+json'
} }
if (config.githubAuthToken && path.indexOf('api.github.com') >= 0) { if (config.token) {
headers['Authorization'] = `token ${config.githubAuthToken}` headers['Authorization'] = `token ${config.token}`
} }
let timeout = fastRaw ? 5000 : config.timeout || 10000 let timeout = fastRaw ? 5000 : config.timeout || 10000
@ -66,31 +66,62 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
if (timedout) { return } if (timedout) { return }
clearTimeout(timer) clearTimeout(timer)
const ac = new AbortController()
let output = '' let output = ''
if (filePath) { if (filePath) {
let file = fs.createWriteStream(filePath) stream.pipeline(res, fs.createWriteStream(filePath), { signal: ac.signal })
res.pipe(file) .then(function() {
resolve({
statusCode: res.statusCode,
status: res.statusCode,
statusMessage: res.statusMessage,
headers: res.headers,
body: null
})
}, function(err) {
if (err.code === 'ABORT_ERR') return
reject(err)
})
// let file = fs.createWriteStream(filePath)
// res.pipe(file)
} else { } else {
res.on('data', function(chunk) { res.on('data', function(chunk) {
output += chunk output += chunk
}) })
} }
res.on('end', function() { res.on('end', function() {
let err = null
if (res.statusCode >= 300 && res.statusCode < 400) { if (res.statusCode >= 300 && res.statusCode < 400) {
if (newRedirects > 5) { if (newRedirects > 5) {
return reject(new Error(`Too many redirects (last one was ${res.headers.location})`)) err = new Error(`Too many redirects (last one was ${res.headers.location})`)
} }
if (!res.headers.location) { else if (!res.headers.location) {
return reject(new Error('Redirect returned no path in location header')) err = new Error('Redirect returned no path in location header')
} }
if (res.headers.location.startsWith('http')) { else if (res.headers.location.startsWith('http')) {
ac.abort()
return resolve(request(config, res.headers.location, filePath, newRedirects, fastRaw)) return resolve(request(config, res.headers.location, filePath, newRedirects, fastRaw))
} else { } else {
ac.abort()
return resolve(request(config, resolveRelative(path, res.headers.location), filePath, newRedirects, fastRaw)) return resolve(request(config, resolveRelative(path, res.headers.location), filePath, newRedirects, fastRaw))
} }
} else if (res.statusCode >= 400) { } else if (res.statusCode >= 400) {
return reject(new Error(`HTTP Error ${res.statusCode}: ${output}`)) err = new Error(`HTTP Error ${res.statusCode}: ${output}`)
} }
if (err) {
ac.abort()
if (!filePath) return reject(err)
// Do some cleanup in case we were in the middle of downloading file
return fs.rm(filePath, function() {
reject(err)
})
}
// Let the pipeline do the resolving so it can finish flusing before calling resolve
if (!filePath) {
resolve({ resolve({
statusCode: res.statusCode, statusCode: res.statusCode,
status: res.statusCode, status: res.statusCode,
@ -98,6 +129,7 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
headers: res.headers, headers: res.headers,
body: output body: output
}) })
}
}) })
}) })
@ -122,7 +154,10 @@ export function request(config, path, filePath = null, redirects, fastRaw = fals
try { try {
res.body = JSON.parse(res.body) res.body = JSON.parse(res.body)
} catch(e) { } catch(e) {
throw new Error(`Error parsing body ${res.body}: ${e.message}`) if (res.body.indexOf('<!DOCTYPE') < 100 || res.body.indexOf('<html') < 100) {
return Promise.reject(new Error('Error parsing body, expected JSON but got HTML instead: ' + res.body))
}
return Promise.reject(new Error(`Error parsing body ${res.body}: ${e.message}`))
} }
} }
} }

View file

@ -2,13 +2,15 @@ import { Low, JSONFile } from 'lowdb'
import { type } from 'os' import { type } from 'os'
import { defaults, isObject } from './defaults.mjs' import { defaults, isObject } from './defaults.mjs'
export default function GetDB(util, log, filename = 'db.json') { export default function GetDB(config, util, log, filename = 'db.json') {
let fullpath = util.getPathFromRoot('./' + filename) let fullpath = util.getPathFromRoot('./' + filename)
const adapter = new JSONFile(fullpath) const adapter = new JSONFile(fullpath)
const db = new Low(adapter) const db = new Low(adapter)
db.id = 'id' db.id = 'id'
db.config = config
db.createId = function(collection) { db.createId = function(collection) {
if (collection.length) { if (collection.length) {
return (collection[collection.length - 1].id || 0) + 1 return (collection[collection.length - 1].id || 0) + 1

64
core/providers/git.mjs Normal file
View file

@ -0,0 +1,64 @@
import { request } from '../client.mjs'
export default class GitProvider {
constructor(config, requester = request) {
this.config = config
this.requestConfig = GitProvider.getRequestConfig(config)
this.requester = requester
}
async getLatestVersion() {
let res = await this.requester(this.requestConfig, this.config.url)
.catch(function(err) {
return Promise.reject(new Error('Failed to get release information, got ' + err.message))
})
if (!Array.isArray(res.body)) {
return Promise.reject(new Error('Body was not a valid git repository release data: ' + JSON.stringify(res.body)))
}
let checked = 0
for (let item of res.body) {
if (!Array.isArray(item.assets)) continue
checked++
for (let asset of item.assets) {
if (!asset.name.endsWith('-sc.7z')
&& !asset.name.endsWith('-sc.zip')) continue
return {
version: item.name,
link: asset.browser_download_url,
filename: asset.name,
log: '',
}
}
}
return Promise.reject(new Error(`No valid service-core release was found, checked ${checked} releases.`))
}
downloadVersion(version, target){
return this.requester(this.requestConfig, version.link, target)
}
static getRequestConfig(config) {
if (config.token) {
return { token: config.token }
}
return {}
}
async checkConfig() {
if (!this.config.url) return Promise.reject(new Error('url was missing in git provider'))
if (typeof(this.config.url) !== 'string') return Promise.reject(new Error('url was not a valid url'))
try { new URL(this.config.url) }
catch (err) { return Promise.reject(new Error('url was not a valid url: ' + err.message)) }
return this.getLatestVersion()
.catch(function(err) {
return Promise.reject(new Error(`Error fetching latest release: ${err.message}`))
})
}
}

0
core/updater.mjs Normal file
View file

View file

@ -16,6 +16,41 @@ export default class Util {
return path.join(this._root_import_meta_url,'../', add) return path.join(this._root_import_meta_url,'../', add)
} }
getExtension(filename) {
let extension = path.extname(filename)
if (filename.indexOf('.tar.') > 0) {
return '.tar' + extension
}
return extension
}
getAppNames(config) {
let out = []
let keys = Object.keys(config)
for (let key of keys) {
if (typeof(config[key]) !== 'object' || config[key] == null) continue
if (typeof(config[key].port) !== 'number' || !config[key].port) continue
if (typeof(config[key].provider) !== 'string' || !config[key].provider) continue
out.push(key)
}
return out
}
get7zipExecutable() {
const util = new Util(import.meta.url)
if (process.platform === 'win32') {
return util.getPathFromRoot('../bin/7za.exe')
}
return util.getPathFromRoot('../bin/7zas')
}
verifyConfig(config) {
if (!config.name) throw new Error('name is missing in config.json')
if (this.getAppNames(config).length === 0) throw new Error('no application was found in config')
if (config.debugPort != null && (typeof(config.debugPort) !== 'number' || !config.debugPort)) throw new Error('debugPort in config not a valid number')
}
runCommand(command, options = [], folder = null, stream = function() {}) { runCommand(command, options = [], folder = null, stream = function() {}) {
return new Promise(function(res, rej) { return new Promise(function(res, rej) {
stream(`[Command] ${folder ? folder : ''}${command} ${options.join(' ')}\n`) stream(`[Command] ${folder ? folder : ''}${command} ${options.join(' ')}\n`)

View file

@ -5,7 +5,18 @@
"main": "lib.mjs", "main": "lib.mjs",
"scripts": { "scripts": {
"dev": "nodemon --watch dev/api --watch core --watch runner.mjs --watch db.mjs --watch log.mjs runner.mjs | bunyan", "dev": "nodemon --watch dev/api --watch core --watch runner.mjs --watch db.mjs --watch log.mjs runner.mjs | bunyan",
"test": "eltro test/**/*.test.mjs -r dot" "test": "eltro test/**/*.test.mjs -r dot",
"test:watch": "npm-watch test"
},
"watch": {
"test": {
"patterns": [
"{core,test}/*"
],
"extensions": "js,mjs",
"quiet": true,
"inherit": true
}
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -1,7 +1,11 @@
import { Eltro as t, assert} from 'eltro' import { Eltro as t, assert} from 'eltro'
import fs from 'fs/promises'
import http from 'http' import http from 'http'
import Util from '../core/util.mjs'
import { request } from '../core/client.mjs' import { request } from '../core/client.mjs'
const util = new Util(import.meta.url)
const testTargetFile = util.getPathFromRoot('./filetest.html')
const port = 61412 const port = 61412
const defaultHandler = function(req, res) { const defaultHandler = function(req, res) {
res.statusCode = 200 res.statusCode = 200
@ -24,6 +28,11 @@ t.before(function(cb) {
server.listen(port, cb) server.listen(port, cb)
}) })
t.after(function() {
return fs.rm(testTargetFile)
.catch(function() { })
})
t.describe('Basics', function() { t.describe('Basics', function() {
t.beforeEach(function() { t.beforeEach(function() {
handler = defaultHandler handler = defaultHandler
@ -57,10 +66,6 @@ t.describe('Basics', function() {
}) })
t.describe('Request', function() { t.describe('Request', function() {
t.beforeEach(function() {
handler = defaultHandler
})
t.test('should work normally', async function() { t.test('should work normally', async function() {
let res = await request({}, prefix) let res = await request({}, prefix)
assert.deepEqual(res.body, {a:1}) assert.deepEqual(res.body, {a:1})
@ -86,6 +91,35 @@ t.describe('Request', function() {
assert.strictEqual(counter, 3) assert.strictEqual(counter, 3)
}) })
t.test('should fail gracefully if HTML is received', async function() {
handler = function(req, res) {
res.statusCode = 200
res.end(`<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
</body>
</html>`);
}
let err = await assert.isRejected(request({}, prefix))
assert.match(err.message, /html/i)
assert.notMatch(err.message, /Unexpected token/i)
handler = function(req, res) {
res.statusCode = 200
res.end(`<html>
<head>
</head>
<body>
</body>
</html>`);
}
err = await assert.isRejected(request({}, prefix))
assert.match(err.message, /html/i)
assert.notMatch(err.message, /Unexpected token/i)
})
t.test('should fail if infinite redirect', async function() { t.test('should fail if infinite redirect', async function() {
const assertRelativeLocation = 'some/text/here' const assertRelativeLocation = 'some/text/here'
const assertLocation = prefix + assertRelativeLocation const assertLocation = prefix + assertRelativeLocation
@ -159,6 +193,89 @@ t.describe('Request', function() {
}) })
}) })
t.describe('Request download', function() {
const assertBody = `<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
</body>
</html>`
t.beforeEach(function() {
handler = defaultHandler
})
t.afterEach(function() {
return fs.rm(testTargetFile)
.catch(function() { })
})
t.test('should support downloading file successfully', async function() {
handler = function(req, res) {
res.statusCode = 200
res.end(assertBody);
}
await request({}, prefix, testTargetFile)
let stat = await fs.stat(testTargetFile)
assert.notStrictEqual(stat.size, 0)
assert.strictEqual(stat.size, assertBody.length)
let contents = await (await fs.readFile(testTargetFile)).toString()
assert.strictEqual(contents, assertBody)
})
t.test('should support downloading file after redirect successfully', async function() {
let counter = 0
handler = function(req, res) {
if (counter < 3) {
res.statusCode = 302
res.setHeader('Location', encodeURI(prefix))
res.end();
counter++
return
}
res.statusCode = 200
res.end(assertBody);
}
await request({}, prefix, testTargetFile)
assert.strictEqual(counter, 3)
let stat = await fs.stat(testTargetFile)
assert.notStrictEqual(stat.size, 0)
assert.strictEqual(stat.size, assertBody.length)
let contents = await (await fs.readFile(testTargetFile)).toString()
assert.strictEqual(contents, assertBody)
})
t.test('should not create file if download redirects too many times', async function() {
handler = function(req, res) {
res.statusCode = 302
res.setHeader('Location', encodeURI(prefix))
res.end(assertBody);
}
let err = await assert.isRejected(request({}, prefix, testTargetFile))
assert.match(err.message, /redirects/)
let errStat = await assert.isRejected(fs.stat(testTargetFile))
assert.strictEqual(errStat.code, 'ENOENT')
})
t.test('should not create file if download fails', async function() {
handler = function(req, res) {
res.statusCode = 404
res.end('{}');
}
let err = await assert.isRejected(request({}, prefix, testTargetFile))
assert.match(err.message, /HTTP/)
assert.match(err.message, /404/)
let errStat = await assert.isRejected(fs.stat(testTargetFile))
assert.strictEqual(errStat.code, 'ENOENT')
})
})
t.after(function(cb) { t.after(function(cb) {
server.close(cb) server.close(cb)
}) })

View file

@ -23,7 +23,7 @@ t.afterEach(function() {
t.test('Should auto create file with some defaults', async function() { t.test('Should auto create file with some defaults', async function() {
await assert.isRejected(fs.stat('./test/db_test.json')) await assert.isRejected(fs.stat('./test/db_test.json'))
let db = await lowdb(util, logger, 'db_test.json') let db = await lowdb({}, util, logger, 'db_test.json')
let stat = await fs.stat('./test/db_test.json') let stat = await fs.stat('./test/db_test.json')
assert.ok(stat.size > 0) assert.ok(stat.size > 0)
@ -34,11 +34,19 @@ t.test('Should auto create file with some defaults', async function() {
assert.notOk(db.data.core.manager) assert.notOk(db.data.core.manager)
}) })
t.test('Should map config to config', async function() {
const assertConfig = { a: 1 }
let db = await lowdb(assertConfig, util, logger, 'db_test.json')
assert.strictEqual(db.config, assertConfig)
})
t.test('Should apply defaults to existing file', async function() { t.test('Should apply defaults to existing file', async function() {
await assert.isRejected(fs.stat('./test/db_test.json')) await assert.isRejected(fs.stat('./test/db_test.json'))
await fs.writeFile('./test/db_test.json', '{"test":true}') await fs.writeFile('./test/db_test.json', '{"test":true}')
let db = await lowdb(util, logger, 'db_test.json') let db = await lowdb({}, util, logger, 'db_test.json')
let stat = await fs.stat('./test/db_test.json') let stat = await fs.stat('./test/db_test.json')
assert.ok(stat.size > 0) assert.ok(stat.size > 0)
@ -55,7 +63,7 @@ t.test('Should not fail if db is invalid', async function() {
await assert.isRejected(fs.stat('./test/db_test.json')) await assert.isRejected(fs.stat('./test/db_test.json'))
await fs.writeFile('./test/db_test.json', '[]') await fs.writeFile('./test/db_test.json', '[]')
let db = await lowdb(util, logger, 'db_test.json') let db = await lowdb({}, util, logger, 'db_test.json')
let stat = await fs.stat('./test/db_test.json') let stat = await fs.stat('./test/db_test.json')
assert.ok(stat.size > 0) assert.ok(stat.size > 0)
@ -72,7 +80,7 @@ t.test('Should not fail if db is invalid', async function() {
t.test('Should support adding an application with defaults', async function() { t.test('Should support adding an application with defaults', async function() {
await assert.isRejected(fs.stat('./test/db_test.json')) await assert.isRejected(fs.stat('./test/db_test.json'))
let db = await lowdb(util, logger, 'db_test.json') let db = await lowdb({}, util, logger, 'db_test.json')
let stat = await fs.stat('./test/db_test.json') let stat = await fs.stat('./test/db_test.json')
assert.ok(stat.size > 0) assert.ok(stat.size > 0)
@ -105,7 +113,7 @@ t.test('Should support reading from db', async function() {
const assertValue = { a: 1 } const assertValue = { a: 1 }
await assert.isRejected(fs.stat('./test/db_test.json')) await assert.isRejected(fs.stat('./test/db_test.json'))
let db = await lowdb(util, logger, 'db_test.json') let db = await lowdb({}, util, logger, 'db_test.json')
db.data.test = assertValue db.data.test = assertValue
@ -113,20 +121,20 @@ t.test('Should support reading from db', async function() {
assert.strictEqual(db.data.test, assertValue) assert.strictEqual(db.data.test, assertValue)
let dbSecondary = await lowdb(util, logger, 'db_test.json') let dbSecondary = await lowdb({}, util, logger, 'db_test.json')
assert.notStrictEqual(dbSecondary.data.test, assertValue) assert.notStrictEqual(dbSecondary.data.test, assertValue)
assert.deepStrictEqual(dbSecondary.data.test, assertValue) assert.deepStrictEqual(dbSecondary.data.test, assertValue)
}) })
t.test('should throw if unable to write to file', function() { t.test('should throw if unable to write to file', function() {
return assert.isRejected(lowdb(util, logger, '../test')) return assert.isRejected(lowdb({}, util, logger, '../test'))
}) })
t.test('should have basic database-like functions defined', async function() { t.test('should have basic database-like functions defined', async function() {
const assertItem1 = { a: 1 } const assertItem1 = { a: 1 }
const assertItem2 = { a: 2 } const assertItem2 = { a: 2 }
const assertItem3 = { a: 3 } const assertItem3 = { a: 3 }
let db = await lowdb(util, logger, 'db_test.json') let db = await lowdb({}, util, logger, 'db_test.json')
assert.strictEqual(db.id, 'id') assert.strictEqual(db.id, 'id')
db.data.myarr = [] db.data.myarr = []
@ -144,7 +152,7 @@ t.test('should have basic database-like functions defined', async function() {
await db.write() await db.write()
let dbSec = await lowdb(util, logger, 'db_test.json') let dbSec = await lowdb({}, util, logger, 'db_test.json')
assert.strictEqual(dbSec.data.myarr.length, 2) assert.strictEqual(dbSec.data.myarr.length, 2)
assert.notStrictEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem1) assert.notStrictEqual(dbSec.get(dbSec.data.myarr, assertItem1.id), assertItem1)
@ -185,7 +193,7 @@ t.test('should have basic database-like functions with string-like name of colle
const assertItem1 = { a: 1 } const assertItem1 = { a: 1 }
const assertItem2 = { a: 2 } const assertItem2 = { a: 2 }
const assertItem3 = { a: 3 } const assertItem3 = { a: 3 }
let db = await lowdb(util, logger, 'db_test.json') let db = await lowdb({}, util, logger, 'db_test.json')
assert.strictEqual(db.id, 'id') assert.strictEqual(db.id, 'id')
db.data.myarr = [] db.data.myarr = []
@ -203,7 +211,7 @@ t.test('should have basic database-like functions with string-like name of colle
await db.write() await db.write()
let dbSec = await lowdb(util, logger, 'db_test.json') let dbSec = await lowdb({}, util, logger, 'db_test.json')
assert.strictEqual(dbSec.data.myarr.length, 2) assert.strictEqual(dbSec.data.myarr.length, 2)
assert.notStrictEqual(dbSec.get('myarr', assertItem1.id), assertItem1) assert.notStrictEqual(dbSec.get('myarr', assertItem1.id), assertItem1)

View file

@ -0,0 +1,97 @@
import { Eltro as t, assert, stub } from 'eltro'
import Util from '../../core/util.mjs'
import fs from 'fs/promises'
import GitProvider from '../../core/providers/git.mjs'
t.timeout(5000).describe('Git integration', function() {
t.after(function() {
return fs.rm('./test/providers/file.7z')
.catch(function() { })
})
t.describe('#getLatestVersion()', function() {
t.test('should return latest version in a valid repository', async function() {
let provider = new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/sc-helloworld/releases' })
let version = await provider.getLatestVersion()
assert.ok(version)
assert.ok(version.version)
assert.ok(version.link)
assert.match(version.link, /\/attachments\//)
})
})
t.describe('#checkConfig()', function() {
t.test('should fail if link does not return json repository object', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'http://git.nfp.is/api/v1/repos/thething/ProgramQueuer' }).checkConfig())
assert.match(err.message, /valid/i)
assert.match(err.message, /repository/i)
err = await assert.isRejected(new GitProvider({ url: 'http://git.nfp.is/api/v1/orgs/nfp/repos' }).checkConfig())
assert.match(err.message, /service-core/i)
assert.match(err.message, /release/i)
})
t.test('should fail if link returns no active release repository with assets', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/thething/eltro/releases' }).checkConfig())
assert.match(err.message, /service-core/i)
assert.match(err.message, /release/i)
})
t.test('should fail on private repositories if token missing', async function() {
let err = await assert.isRejected(new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases' }).checkConfig())
assert.match(err.message, /fail/i)
assert.match(err.message, /404/i)
assert.match(err.message, /release/i)
})
t.test('should otherwise succeed', function() {
return new GitProvider({ url: 'https://git.nfp.is/api/v1/repos/TheThing/sc-manager/releases' })
.checkConfig()
})
let test = t
if (!process.env.gittesttoken) {
test = test.skip()
}
test.test('should succeed on private repo if token is provided', function() {
return new GitProvider({
token: process.env.gittesttoken.trim(),
url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases',
}).checkConfig()
})
})
t.describe('#downloadVersion()', function() {
const util = new Util(import.meta.url)
let test = t
if (!process.env.gittesttoken) {
test = test.skip()
}
test.test('should successfully download release', async function() {
let provider = new GitProvider({
token: process.env.gittesttoken.trim(),
url: 'https://git.nfp.is/api/v1/repos/TheThing/privateexample/releases',
})
await provider.checkConfig()
let version = await provider.getLatestVersion()
assert.ok(version.version)
assert.ok(version.filename)
assert.ok(version.link)
let path = util.getPathFromRoot('./file.7z')
await provider.downloadVersion(version, path)
let stat = await fs.stat(path)
assert.ok(stat.size > 0)
stat = await fs.stat('./test/providers/file.7z')
assert.ok(stat.size > 0)
let output = ''
await util.runCommand(util.get7zipExecutable(), ['l', 'file.7z'], util.getPathFromRoot('./'), function(chunk) {
output += chunk + '\n'
})
assert.ok(output.indexOf('file1.txt'))
assert.ok(output.indexOf('file2.txt'))
})
})
})

210
test/providers/git.test.mjs Normal file
View file

@ -0,0 +1,210 @@
import { Eltro as t, assert, stub } from 'eltro'
import GitProvider from '../../core/providers/git.mjs'
t.describe('#getLatestVersion()', function() {
t.test('should return correct name and link in result', async function() {
const assertName = 'Karen'
const assertLink = 'Over The Future'
const assertFilename = 'test-sc.7z'
let stubber = stub()
let provider = new GitProvider({}, stubber)
stubber.resolves({ body: [
{ name: assertName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
assert.strictEqual(version.log, '')
})
t.test('should support multiple extension', async function() {
const assertName = 'Karen'
const assertLink = 'My Wings'
const assertFilename = 'test-sc.zip'
let stubber = stub()
let provider = new GitProvider({}, stubber)
stubber.resolves({ body: [
{ name: assertName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
assert.strictEqual(version.log, '')
})
t.test('should skip versions with no assets', async function() {
const assertName = 'name1'
const assertLink = 'somelink'
const assertFilename = 'something-sc.7z'
let stubber = stub()
let provider = new GitProvider({}, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [] },
{ name: assertName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
})
t.test('should skip versions with non-sc filename', async function() {
const assertName = 'name2'
const assertLink = 'somelink2'
const assertFilename = 'something-sc.7z'
let stubber = stub()
let provider = new GitProvider({}, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [{ name: 'nope.7z', browser_download_url: 'nope' }] },
{ name: assertName, assets: [{ name: assertFilename, browser_download_url: assertLink }] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
})
t.test('should skip assets with non-sc filename', async function() {
const assertName = 'name3'
const assertLink = 'somelink3'
const assertFilename = 'something-sc.7z'
let stubber = stub()
let provider = new GitProvider({}, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [{ name: 'nope.7z', browser_download_url: 'nope' }] },
{ name: assertName, assets: [
{ name: 'nope.7z', browser_download_url: 'nope' },
{ name: assertFilename, browser_download_url: assertLink },
] },
]})
let version = await provider.getLatestVersion()
assert.strictEqual(version.version, assertName)
assert.strictEqual(version.link, assertLink)
assert.strictEqual(version.filename, assertFilename)
})
t.test('should otherwise reject', async function() {
let stubber = stub()
let provider = new GitProvider({}, stubber)
stubber.resolves({ body: [
{ name: 'test', assets: [{ name: 'nope.7z', browser_download_url: 'nope' }] },
{ name: 'test2', assets: [{ name: 'nope2.7z', browser_download_url: 'nope' },] },
{ name: 'test3', assets: [{ name: 'nope2.7z', browser_download_url: 'nope' },] },
]})
let err = await assert.isRejected(provider.getLatestVersion())
assert.match(err.message, /release/)
assert.match(err.message, /found/)
})
t.test('should reject if not valid body', async function() {
let provider = new GitProvider({}, stub())
provider.requester.resolves({ body: { } })
let err = await assert.isRejected(provider.getLatestVersion())
assert.match(err.message, /valid/)
assert.match(err.message, /repository/)
assert.match(err.message, /release/)
})
t.test('should wrap rejected errors', async function() {
const assertError = new Error('Oosegoto')
let provider = new GitProvider({}, stub())
provider.requester.rejects(assertError)
let err = await assert.isRejected(provider.getLatestVersion())
assert.notStrictEqual(err, assertError)
assert.match(err.message, /failed/i)
assert.match(err.message, /release/i)
assert.match(err.message, new RegExp(assertError.message))
})
})
t.describe('#downloadVersion()', function() {
t.test('should call requester with correct url and info', async function() {
const assertError = new Error('Moeyuru Monmon Mousou Zou')
const assertConfig = { a: 1 }
const assertUrl = 'http://test'
const assertTarget = '/some/path/here'
let provider = new GitProvider({}, stub())
provider.requestConfig = assertConfig
provider.requester.rejects(assertError)
let err = await assert.isRejected(provider.downloadVersion({
link: assertUrl,
}, assertTarget))
assert.strictEqual(err, assertError)
assert.strictEqual(provider.requester.firstCall[0], assertConfig)
assert.strictEqual(provider.requester.firstCall[1], assertUrl)
assert.strictEqual(provider.requester.firstCall[2], assertTarget)
})
})
t.describe('#getRequestConfig()', function() {
t.test('should default return empty object', function() {
assert.deepStrictEqual(GitProvider.getRequestConfig({}), {})
assert.deepStrictEqual(GitProvider.getRequestConfig({ asdf: '1234' }), {})
assert.deepStrictEqual(GitProvider.getRequestConfig({ url: 'http://test' }), {})
assert.deepStrictEqual(GitProvider.getRequestConfig({ a: 1 }), {})
})
t.test('should return token if token is defined', function() {
const assertToken = 'asdf1234'
assert.deepStrictEqual(GitProvider.getRequestConfig({ token: assertToken }), { token: assertToken })
assert.deepStrictEqual(GitProvider.getRequestConfig({ token: assertToken, asdf: '1234' }), { token: assertToken })
assert.deepStrictEqual(GitProvider.getRequestConfig({ token: assertToken, url: 'http://test' }), { token: assertToken })
assert.deepStrictEqual(GitProvider.getRequestConfig({ token: assertToken, a: 1 }), { token: assertToken })
})
})
t.describe('#checkConfig()', function() {
t.test('should fail if missing url in config', async function() {
let err = await assert.isRejected(new GitProvider({}).checkConfig())
assert.match(err.message, /url/i)
assert.match(err.message, /missing/i)
})
t.test('should fail if url is something else than an url', async function() {
let tests = [[], {}, 1234, 'asdf', 'http://']
let errors = await Promise.all(tests.map(function(check) {
return assert.isRejected(new GitProvider({ url: check }).checkConfig(), `Promise fulfilled with url = ${JSON.stringify(check)}`)
}))
for (let err of errors) {
assert.match(err.message, /url/i)
assert.match(err.message, /valid/i)
}
})
t.test('should call requester with correct config and url', async function() {
const assertError = new Error('Toki wo Koeta Yoru')
const assertRequestConfig = { a: 1 }
const assertUrl = 'http://test'
let provider = new GitProvider({ url: assertUrl }, stub())
provider.requestConfig = assertRequestConfig
provider.requester.rejects(assertError)
let err = await assert.isRejected(provider.checkConfig())
assert.notStrictEqual(err, assertError)
assert.match(err.message, new RegExp(assertError.message))
assert.strict(provider.requester.firstCall[0], assertRequestConfig)
assert.strict(provider.requester.firstCall[1], assertUrl)
})
})

View file

@ -31,6 +31,156 @@ t.describe('#getUrlFromRoot()', function() {
var util = new Util(import.meta.url) var util = new Util(import.meta.url)
let data = await import(util.getUrlFromRoot('template.mjs')) let data = await import(util.getUrlFromRoot('template.mjs'))
assert.deepEqual(data.default, { a: 1 }) assert.deepStrictEqual(data.default, { a: 1 })
})
})
t.describe('#get7zipExecutable()', function() {
var util = new Util(import.meta.url)
if (process.platform === 'win32') {
console.log('Adding 7zip windows exe path test')
t.test('should return windows executable path', function() {
assert.ok(util.get7zipExecutable().endsWith('\\service-core\\bin\\7za.exe'), `${util.get7zipExecutable()} should end with 7za.exe`)
})
} else {
console.log('Adding 7zip linux exe path test')
t.test('should return linux executable path', function() {
assert.ok(util.get7zipExecutable().endsWith('/service-core/bin/7zas'), `${util.get7zipExecutable()} should end with 7zas`)
})
}
})
t.describe('#getExtension()', function() {
var util = new Util(import.meta.url)
t.test('should return correct extension on basic extension types', function() {
assert.strictEqual(util.getExtension('file.7z'), '.7z')
assert.strictEqual(util.getExtension('file.zip'), '.zip')
assert.strictEqual(util.getExtension('file.tar'), '.tar')
assert.strictEqual(util.getExtension('file.doc'), '.doc')
assert.strictEqual(util.getExtension('file.rar'), '.rar')
assert.strictEqual(util.getExtension('file.test'), '.test')
})
t.test('should support that annoying tar extension', function() {
assert.strictEqual(util.getExtension('file.tar.test'), '.tar.test')
assert.strictEqual(util.getExtension('file.tar.gz'), '.tar.gz')
assert.strictEqual(util.getExtension('file.tar.xz'), '.tar.xz')
assert.strictEqual(util.getExtension('file.tar.bz2'), '.tar.bz2')
})
})
t.describe('#getApplications()', function() {
var util = new Util(import.meta.url)
t.test('should fail to find if not a valid object', function() {
assert.deepStrictEqual(util.getAppNames({ app: [] }), [])
assert.deepStrictEqual(util.getAppNames({ app: 1234 }), [])
assert.deepStrictEqual(util.getAppNames({ app: '124124' }), [])
assert.deepStrictEqual(util.getAppNames({ app: '' }), [])
assert.deepStrictEqual(util.getAppNames({ app: {} }), [])
assert.deepStrictEqual(util.getAppNames({ app: null }), [])
})
t.test('should return the name of the key if an object with port and provider is found', function() {
assert.deepStrictEqual(util.getAppNames({ app: { port: 1234, provider: 'asdf' } }), ['app'])
})
t.test('should fail to find if port is missing or port not a number', function() {
assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: null } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 'asdf' } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: '1234' } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: 0 } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: [] } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: 'asdf', port: {} } }), [])
})
t.test('should fail to find if provider is missing or not a string', function() {
assert.deepStrictEqual(util.getAppNames({ app: { port: 1234 } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: '', port: 1234 } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: null, port: 1234 } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: [], port: 1234 } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: {}, port: 1234 } }), [])
assert.deepStrictEqual(util.getAppNames({ app: { provider: 1234, port: 1234 } }), [])
})
})
t.describe('#verifyConfig()', function() {
var util = new Util(import.meta.url)
let input
t.beforeEach(function() {
input = {
name: 'test',
title: 'Test',
description: 'Some description',
app: { port: 1234, provider: 'asdf' },
}
})
let testMissing = ['name']
testMissing.forEach(function(check) {
t.test(`should fail on missing ${check}`, function() {
delete input[check]
assert.throws(function() {
util.verifyConfig(input)
}, function(err) {
assert.match(err.message, new RegExp(check))
return true
})
})
})
let testOptional = ['title', 'description']
testOptional.forEach(function(check) {
t.test(`should succeed even if ${check} is missing`, function() {
delete input[check]
util.verifyConfig(input)
})
})
t.test('should succeed if debug port is specified or null', function() {
input.debugPort = 1234
util.verifyConfig(input)
input.debugPort = null
util.verifyConfig(input)
})
t.test('should fail if debug port is invalid', function() {
let checks = [
[],
{},
0,
'asdf',
'1234'
]
checks.forEach(function(check) {
input.debugPort = check
assert.throws(function() {
util.verifyConfig(input)
}, function(err) {
assert.match(err.message, /debugPort/)
assert.match(err.message, /number/)
return true
})
})
})
t.test('should fail if no objects in config', function() {
delete input.app
assert.throws(function() {
util.verifyConfig(input)
}, function(err) {
assert.match(err.message, /no/)
assert.match(err.message, /app/)
return true
})
}) })
}) })