diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 692dbd6..4576447 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -1,8 +1,15 @@ env: node: true + es6: true es2021: true + es2024: true extends: eslint:recommended +parser: '@babel/eslint-parser' parserOptions: + babelOptions: + configFile: false + plugins: [ "@babel/plugin-syntax-import-attributes" ] + requireConfigFile: false ecmaVersion: latest sourceType: module rules: {} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18e9650..b1f034e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: - name: Start MySQL run: sudo /etc/init.d/mysql start - uses: actions/setup-node@v4 + with: + node-version: 20 - uses: actions/checkout@v4 - run: npm install - name: Initialize MySQL @@ -43,12 +45,13 @@ jobs: active: ${{ steps.get.outputs.active }} test: - needs: [ lint, get-lts ] + needs: [ get-lts ] runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] - node-version: ${{ fromJson(needs.get-lts.outputs.active) }} + # node-version: ${{ fromJson(needs.get-lts.outputs.active) }} + node-version: [ 20 ] fail-fast: false steps: - run: sudo /etc/init.d/mysql start @@ -61,11 +64,12 @@ jobs: - run: npm test test-mac: - needs: [ lint, get-lts ] + needs: [ get-lts ] runs-on: macos-latest strategy: matrix: - node-version: ${{ fromJson(needs.get-lts.outputs.active) }} + # node-version: ${{ fromJson(needs.get-lts.outputs.active) }} + node-version: [ 20 ] fail-fast: false steps: - name: Install & Start MySQL @@ -84,11 +88,12 @@ jobs: test-win: # if: false - needs: [ lint, get-lts ] + needs: [ get-lts ] runs-on: windows-latest strategy: matrix: - node-version: ${{ fromJson(needs.get-lts.outputs.active) }} + node-version: [ 20 ] + # node-version: ${{ fromJson(needs.get-lts.outputs.active) }} experimental: [true] fail-fast: false steps: @@ -102,4 +107,4 @@ jobs: node-version: ${{ matrix.node-version }} - run: sh sql/init-mysql.sh - run: npm install - - run: npm test + - run: sh test.sh diff --git a/README.md b/README.md index 78c63e6..522e7ae 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ npm install Edit the files in conf.d to reflect your local settings. -Each config file has a default section which lists all available config settings. Below the `default` section are optional deployment environments such as `production`, `development`, and `test`. When a config file is loaded, the environment variable `NODE_ENV` is checked and if defined, any overrides in the matching deployment section are applied. +Each config file has a default section which lists all available config settings. Below the `default` section are optional deployment environments such as `production`, `development`, and `test`. When a deployment environment is detected, overrides in the matching deployment section are applied. ## Start the service @@ -33,5 +33,113 @@ or `npm run develop (development)` -will start up the HTTP service on the port specified in conf.d/http.yml. +will start up the HTTP service on the port specified in `conf.d/http.yml`. The default URL for the service is [http://localhost:3000](http://localhost:3000) and the API methods have documentation at [http://localhost:3000/documentation](http://localhost:3000/documentation). + + +## Using the API service + +Until the NicTool 3.0 HTTP client is written, using a web browser (in Developer mode) or a CLI HTTP utility like curl can be used. Here's a quick tutorial: + +### Start a New Session + +`curl -X POST http://localhost:3000/session` + +```json +{"statusCode":400,"error":"Bad Request","message":"Invalid request payload input"} +``` + +The request was rejected because it's missing the required parameters, as shown in the documentation. Create a file called nt-auth.json and store the credentials of a NicTool user therein. Then try the auth request again: + +`curl -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json` + +```json +{"user":{"id":4096,"first_name":"Unit","last_name":"Test","username":"unit-test","email":"unit-test@example.com"},"group":{"id":4096,"name":"example.com"},"session":{"id":162},"meta":{"api":{"version":"3.0.0"},"msg":"you are logged in"} +``` + +That's not the easiest to read so lets pipe it through `json_pp`: + +`curl -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json | json_pp` + +```json +{ + "group" : { + "id" : 4096, + "name" : "example.com" + }, + "meta" : { + "api" : { + "version" : "3.0.0" + }, + "msg" : "you are logged in" + }, + "session" : { + "id" : 162 + }, + "user" : { + "email" : "unit-test@example.com", + "first_name" : "Unit", + "id" : 4096, + "last_name" : "Test", + "username" : "unit-test" + } +} +``` + +Now we're talking. But we're missing something. The point of sending `POST /session` is to establish a session we can use with subsequent requests. Let's also take a look at the HTTP response headers with the `-i` option to curl. + +``` +~ curl -i -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json +HTTP/1.1 200 OK +content-type: application/json; charset=utf-8 +cache-control: no-cache +set-cookie: sid-nictool=Fe26.2**19f7d4f243faa77b048119b4a2bcbdcaa7826cdd853d8bdd3110f330ac6932c8*pzn_-OSy1SfoNpWbNvY3xw*RZQ8EgV2IGphwBz-Fb0AvBGofBwct-GnExEdxW-P-mtc1CWLuBJF0IyI7da_tMtp**07d92c1e89978b270fbdd449adcecbab3078b746c4167fe586f417be866c54d8*nDSOqzX79qmsztrHHjub7FgC7XiAxqGNdB-txLq8L84; Max-Age=3600; Expires=Sun, 25 Feb 2024 21:51:20 GMT; HttpOnly; SameSite=Strict; Path=/ +content-length: 237 +Date: Sun, 25 Feb 2024 20:51:20 GMT +Connection: keep-alive +Keep-Alive: timeout=5 + +{"user":{"id":4096,"first_name":"Unit","last_name":"Test","username":"unit-test","email":"unit-test@example.com"},"group":{"id":4096,"name":"example.com"},"session":{"id":162},"meta":{"api":{"version":"3.0.0"},"msg":"you are logged in"}} +``` + +Notice the `set-cookie` header. We can add that cookie to each CLI request, making the requests very long, or save the cookie to a `cookie-jar` file, and then tell curl to sent that cookie with future requests: + +``` +curl --cookie-jar nt-session -X POST http://localhost:3000/session --header "Content-Type: application/json" -d @nt-auth.json +{"user":{"id":4096,"first_name":"Unit","last_name":"Test","username":"unit-test","email":"unit-test@example.com"},"group":{"id":4096,"name":"example.com"},"session":{"id":162},"meta":{"api":{"version":"3.0.0"},"msg":"you are logged in"}} +``` + +and if we peek inside the cookie jar: + +```sh +➜ ~ cat nt-session +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +#HttpOnly_localhost FALSE / FALSE 1708898204 sid-nictool Fe26.2**7a4db1aa0d250c5ba5dda0560ef6cb2c33652f412ee385ebe022313f4fd206f1*g8kgix2HyZUvCKdc60ITMA*Pk3tlc4lYvDAs2J_ZyVHOhYyKWAsGZzbkMdHleLxNPQ55EDmO0vfZWTSILzhceQn**46883c6f21a76dddc10d7c1b0bc3a82302b989057bed459fe61f00eba7d7cacd*bBpV_eKE8VJEz-IDDobcI0nmJT54IndUmoWfE1Eu4fM +``` + +We can see that our session cookie has been saved. Now we can make other requests to the API using that session cookie: + +``` + curl -b nt-session -X GET http://localhost:3000/user/4096 --header "Content-Type: application/json" | json_pp +{ + "group" : { + "id" : 4096 + }, + "meta" : { + "api" : { + "version" : "3.0.0" + }, + "msg" : "here's your user" + }, + "user" : { + "email" : "unit-test@example.com", + "first_name" : "Unit", + "id" : 4096, + "last_name" : "Test", + "username" : "unit-test" + } +} +``` diff --git a/lib/config.js b/lib/config.js index 4ed446e..d9b2fab 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,16 +1,26 @@ -const fs = require('fs/promises') +import fs from 'node:fs/promises' +import fsSync from 'node:fs' -const YAML = require('yaml') +import YAML from 'yaml' + +import { setEnv } from './util.js' +setEnv() class Config { constructor(opts = {}) { this.cfg = {} - this.debug = process.env.NODE_DEBUG ? true : false - this.env = process.env.NODE_ENV ?? opts.env + this.getEnv(opts) + } + + async getEnv(opts = {}) { + this.env = process.env.NODE_ENV ?? opts.env ?? '' + this.debug = Boolean(process.env.NODE_DEBUG) if (this.debug) console.log(`debug: true, env: ${this.env}`) } async get(name, env) { + this.getEnv() + const cacheKey = [name, env ?? this.env].join(':') if (this.cfg?.[cacheKey]) return this.cfg[cacheKey] // cached @@ -23,10 +33,12 @@ class Config { } getSync(name, env) { + this.getEnv() + const cacheKey = [name, env ?? this.env].join(':') if (this.cfg?.[cacheKey]) return this.cfg[cacheKey] // cached - const str = require('fs').readFileSync(`./conf.d/${name}.yml`, 'utf8') + const str = fsSync.readFileSync(`./conf.d/${name}.yml`, 'utf8') const cfg = YAML.parse(str) this.cfg[cacheKey] = applyDefaults(cfg[env ?? this.env], cfg.default) @@ -36,7 +48,7 @@ class Config { function applyDefaults(cfg = {}, defaults = {}) { for (const d in defaults) { - if (cfg[d] === undefined) { + if ([undefined, null].includes(cfg[d])) { cfg[d] = defaults[d] } else if (typeof cfg[d] === 'object' && typeof defaults[d] === 'object') { cfg[d] = applyDefaults(cfg[d], defaults[d]) @@ -45,4 +57,4 @@ function applyDefaults(cfg = {}, defaults = {}) { return cfg } -module.exports = new Config() +export default new Config() diff --git a/lib/config.test.js b/lib/config.test.js index c10f777..65cf517 100644 --- a/lib/config.test.js +++ b/lib/config.test.js @@ -1,46 +1,59 @@ -const assert = require('node:assert/strict') -const { describe, it } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' -const config = require('./config') +import Config from './config.js' -describe('config', function () { - describe('get', function () { - it(`loads mysql test config`, async function () { - const cfg = await config.get('mysql', 'test') +describe('config', () => { + describe('get', () => { + it(`loads mysql test config`, async () => { + const cfg = await Config.get('mysql', 'test') assert.deepEqual(cfg, mysqlTestCfg) }) - it(`loads mysql test config syncronously`, function () { - const cfg = config.getSync('mysql', 'test') + it(`loads mysql test config syncronously`, () => { + const cfg = Config.getSync('mysql', 'test') assert.deepEqual(cfg, mysqlTestCfg) }) - it(`loads mysql cov config`, async function () { - const cfg = await config.get('mysql', 'cov') + it(`loads mysql cov config`, async () => { + const cfg = await Config.get('mysql', 'cov') assert.deepEqual(cfg, mysqlTestCfg) }) - it(`loads mysql cov config (from cache)`, async function () { + it(`loads mysql cov config (from cache)`, async () => { process.env.NODE_DEBUG = 1 - const cfg = await config.get('mysql', 'cov') + const cfg = await Config.get('mysql', 'cov') assert.deepEqual(cfg, mysqlTestCfg) process.env.NODE_DEBUG = '' }) - it(`loads session test config`, async function () { - const cfg = await config.get('session', 'test') + it(`loads session test config`, async () => { + const cfg = await Config.get('session', 'test') assert.deepEqual(cfg, sessCfg) }) - it(`loads session test config syncronously`, function () { - const cfg = config.getSync('session', 'test') + it(`loads session test config syncronously`, () => { + const cfg = Config.getSync('session', 'test') assert.deepEqual(cfg, sessCfg) }) - it(`loads http test config syncronously`, function () { - const cfg = config.getSync('http', 'test') + it(`loads http test config syncronously`, () => { + const cfg = Config.getSync('http', 'test') assert.deepEqual(cfg, httpCfg) }) + + it(`detects NODE_DEBUG env`, async () => { + process.env.NODE_DEBUG = 1 + let cfg = await Config.get('mysql', 'test') + assert.equal(Config.debug, true) + + process.env.NODE_DEBUG = '' + cfg = await Config.get('mysql', 'test') + assert.equal(Config.debug, false) + + cfg = await Config.get('mysql', 'test') + assert.equal(cfg.user, 'root') + }) }) }) diff --git a/lib/group.js b/lib/group.js index d29c823..17157dd 100644 --- a/lib/group.js +++ b/lib/group.js @@ -1,10 +1,12 @@ -const Mysql = require('./mysql') -const Util = require('./util') +import Mysql from './mysql.js' +import { mapToDbColumn } from './util.js' const groupDbMap = { id: 'nt_group_id', parent_gid: 'parent_group_id' } class Group { - constructor() {} + constructor() { + this.mysql = Mysql + } async create(args) { if (args.id) { @@ -14,14 +16,17 @@ class Group { return await Mysql.insert( `INSERT INTO nt_group`, - Util.mapToDbColumn(args, groupDbMap), + mapToDbColumn(args, groupDbMap), ) } async get(args) { return await Mysql.select( - `SELECT nt_group_id AS id, name FROM nt_group WHERE`, - Util.mapToDbColumn(args, groupDbMap), + `SELECT nt_group_id AS id + , parent_group_id AS parent_gid + , name + FROM nt_group WHERE`, + mapToDbColumn(args, groupDbMap), ) } @@ -32,8 +37,22 @@ class Group { , parent_group_id AS parent_gid , deleted FROM nt_group WHERE`, - Util.mapToDbColumn(args, groupDbMap), + mapToDbColumn(args, groupDbMap), + ) + } + + async put(args) { + if (!args.id) return false + const id = args.id + delete args.id + // Mysql.debug(1) + const r = await Mysql.update( + `UPDATE nt_group SET`, + `WHERE nt_group_id=${id}`, + mapToDbColumn(args, groupDbMap), ) + // console.log(r) + return r.changedRows === 1 } async delete(args, val) { @@ -54,5 +73,4 @@ class Group { } } -module.exports = new Group() -module.exports._mysql = Mysql +export default new Group() diff --git a/lib/group.test.js b/lib/group.test.js index 14abab2..8aaccac 100644 --- a/lib/group.test.js +++ b/lib/group.test.js @@ -1,35 +1,55 @@ -const assert = require('node:assert/strict') -const { describe, it, after } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it, after, before } from 'node:test' -const group = require('./group') +import Group from './group.js' + +import testCase from './test/group.json' with { type: 'json' } after(async () => { - group._mysql.disconnect() + Group.mysql.disconnect() }) describe('group', function () { + before(async () => { + await Group.create(testCase) + }) + it('gets group by id', async () => { - const g = await group.get({ id: 4096 }) + const g = await Group.get({ id: testCase.id }) assert.deepEqual(g[0], { - id: 4096, - name: 'example.com', + id: testCase.id, + name: testCase.name, + parent_gid: 0, }) }) it('gets group by name', async () => { - const u = await group.get({ name: 'example.com' }) - assert.deepEqual(u[0], { - id: 4096, - name: 'example.com', + const g = await Group.get({ name: testCase.name }) + assert.deepEqual(g[0], { + id: testCase.id, + name: testCase.name, + parent_gid: 0, }) }) - it('delete a group', async () => { - assert.ok(await group.delete({ id: 4096 })) - let u = await group.getAdmin({ id: 4096 }) - assert.equal(u[0].deleted, 1) - await group.delete({ id: 4096 }, 0) // restore - u = await group.getAdmin({ id: 4096 }) - assert.equal(u[0].deleted, 0) + it('changes a group', async () => { + assert.ok(await Group.put({ id: testCase.id, name: 'example.net' })) + assert.deepEqual(await Group.get({ id: testCase.id }), [ + { + id: testCase.id, + name: 'example.net', + parent_gid: 0, + }, + ]) + assert.ok(await Group.put({ id: testCase.id, name: testCase.name })) + }) + + it('deletes a group', async () => { + assert.ok(await Group.delete({ id: testCase.id })) + let g = await Group.getAdmin({ id: testCase.id }) + assert.equal(g[0].deleted, 1) + await Group.delete({ id: testCase.id }, 0) // restore + g = await Group.getAdmin({ id: testCase.id }) + assert.equal(g[0].deleted, 0) }) }) diff --git a/lib/mysql.js b/lib/mysql.js index 35516b4..c31eeed 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -1,37 +1,38 @@ -// const crypto = require('crypto') -const mysql = require('mysql2/promise') +import mysql from 'mysql2/promise' -const util = require('./util') -util.setEnv() -const config = require('./config') +import Config from './config.js' -class MySQL { +let _debug + +class Mysql { constructor() { - this._debug = config.debug + if (Mysql._instance) return Mysql._instance + Mysql._instance = this + this.debug(Config.debug) } async connect() { // if (this.dbh && this.dbh?.connection?.connectionId) return this.dbh; - const cfg = await config.get('mysql') - if (this._debug) console.log(cfg) + const cfg = await Config.get('mysql') + if (_debug) console.log(cfg) this.dbh = await mysql.createConnection(cfg) - if (this._debug) + if (_debug) console.log(`MySQL connection id ${this.dbh.connection.connectionId}`) return this.dbh } async execute(query, paramsArray) { if (!this.dbh || this.dbh?.connection?._closing) { - if (this._debug) console.log(`(re)connecting to MySQL`) + if (_debug) console.log(`(re)connecting to MySQL`) this.dbh = await this.connect() } - if (this._debug) console.log(query) - if (this._debug) console.log(paramsArray) + if (_debug) console.log(query) + if (_debug) console.log(paramsArray) const [rows, fields] = await this.dbh.execute(query, paramsArray) - if (this._debug) { + if (_debug) { if (fields) console.log(fields) console.log(rows) } @@ -42,16 +43,42 @@ class MySQL { } async insert(query, params = {}) { + const skipExecute = params.skipExecute ?? false + delete params.skipExecute + query += `(${Object.keys(params).join(',')}) VALUES(${Object.keys(params).map(() => '?')})` + + if (skipExecute) return query return await this.execute(query, Object.values(params)) } async select(query, params = {}) { + const skipExecute = params.skipExecute ?? false + delete params.skipExecute + + const [queryWhere, paramsArray] = this.whereConditions(query, params) + + if (skipExecute) return queryWhere + return await this.execute(queryWhere, paramsArray) + } + + async update(query, where, params = {}) { + const skipExecute = params.skipExecute ?? false + delete params.skipExecute + + query += ` ${Object.keys(params).join('=?,')}=? ${where}` + + if (skipExecute) return { q: query, p: Object.values(params) } + return await this.execute(query, Object.values(params)) + } + + whereConditions(query, params) { let paramsArray = [] + if (Array.isArray(params)) { paramsArray = [...params] } else if (typeof params === 'object' && !Array.isArray(params)) { - // Object to SQL. Eg. { id: 'sample' } -> SELECT...WHERE id=?, ['sample'] + // Object to WHERE conditions let first = true for (const p in params) { if (!first) query += ' AND' @@ -60,21 +87,24 @@ class MySQL { first = false } } + return [query, paramsArray] + } - return await this.execute(query, paramsArray) + async delete(query, params) { + const [queryWhere, paramsArray] = this.whereConditions(query, params) + return await this.execute(queryWhere, paramsArray) } async disconnect(dbh) { const d = dbh || this.dbh - if (this._debug) - console.log(`MySQL connection id ${d.connection.connectionId}`) - await d.end() + if (_debug) console.log(`MySQL connection id ${d.connection.connectionId}`) + if (d) await d.end() } debug(val) { - if (val !== undefined) this._debug = val - return this._debug + if (val !== undefined) _debug = val + return _debug } } -module.exports = new MySQL() +export default new Mysql() diff --git a/lib/mysql.test.js b/lib/mysql.test.js index 3edebe2..82a6144 100644 --- a/lib/mysql.test.js +++ b/lib/mysql.test.js @@ -1,29 +1,80 @@ -const assert = require('node:assert/strict') -const { describe, it } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' -const mysql = require('./mysql') +import Mysql from './mysql.js' describe('mysql', () => { + let dbh + it('connects', async () => { - this.dbh = await mysql.connect() - assert.ok(this.dbh.connection.connectionId) + dbh = await Mysql.connect() + assert.ok(dbh.connection.connectionId) }) if (process.env.NODE_ENV === 'cov') { it('is noisy when debug=true', async () => { - mysql.debug(true) - await mysql.execute(`SHOW DATABASES`) - await mysql.select(`SELECT * FROM nt_group`) - mysql.debug(false) + Mysql.debug(true) + await Mysql.execute(`SHOW DATABASES`) + await Mysql.select(`SELECT * FROM nt_group`) + Mysql.debug(false) }) } - it.todo('SQL: formats SELECT queries', async () => {}) + it('SQL: formats SELECT queries', async () => { + const r = await Mysql.select(`SELECT * FROM nt_user WHERE`, { + last_name: 'Test', + skipExecute: true, + }) + assert.equal(r, `SELECT * FROM nt_user WHERE last_name=?`) + }) + + it('SQL: formats INSERT queries', async () => { + const r = await Mysql.select(`INSERT INTO nt_user SET`, { + first_name: 'uNite', + last_name: 'Test', + skipExecute: true, + }) + assert.equal(r, `INSERT INTO nt_user SET first_name=? AND last_name=?`) + }) - it.todo('SQL: formats INSERT queries', async () => {}) + it('SQL: formats UPDATE queries, 1', async () => { + const { q, p } = await Mysql.update( + `UPDATE nt_user SET`, + `WHERE nt_user_id=4096`, + { first_name: 'uNite', skipExecute: true }, + ) + assert.equal(q, `UPDATE nt_user SET first_name=? WHERE nt_user_id=4096`) + assert.deepEqual(p, ['uNite']) + }) + + it('SQL: formats UPDATE queries, 2', async () => { + const { q, p } = await Mysql.update( + `UPDATE nt_user SET`, + `WHERE nt_user_id=4096`, + { last_name: 'Teste', is_admin: 1, skipExecute: true }, + ) + assert.equal( + q, + `UPDATE nt_user SET last_name=?,is_admin=? WHERE nt_user_id=4096`, + ) + assert.deepEqual(p, ['Teste', 1]) + }) + + it('SQL: formats UPDATE queries, 3', async () => { + const { q, p } = await Mysql.update( + `UPDATE nt_user SET`, + `WHERE nt_user_id=4096`, + { first_name: 'Unit', last_name: 'Test', is_admin: 0, skipExecute: true }, + ) + assert.equal( + q, + `UPDATE nt_user SET first_name=?,last_name=?,is_admin=? WHERE nt_user_id=4096`, + ) + assert.deepEqual(p, ['Unit', 'Test', 0]) + }) it('disconnects', async () => { - assert.ok(this.dbh.connection.connectionId) - await mysql.disconnect(this.dbh) + assert.ok(dbh.connection.connectionId) + await Mysql.disconnect(dbh) }) }) diff --git a/lib/permission.js b/lib/permission.js new file mode 100644 index 0000000..a1ad40c --- /dev/null +++ b/lib/permission.js @@ -0,0 +1,138 @@ +import Mysql from './mysql.js' +import { mapToDbColumn } from './util.js' + +const permDbMap = { + id: 'nt_perm_id', + uid: 'nt_user_id', + gid: 'nt_group_id', + inherit: 'inherit_perm', + name: 'perm_name', +} + +const boolFields = [ + 'group_create', + 'group_delete', + 'group_write', + 'nameserver_create', + 'nameserver_delete', + 'nameserver_write', + 'self_write', + 'user_create', + 'user_delete', + 'user_write', + 'zone_create', + 'zone_delegate', + 'zone_delete', + 'zone_write', + 'zonerecord_create', + 'zonerecord_delegate', + 'zonerecord_delete', + 'zonerecord_write', + 'inherit', + 'deleted', +] + +class Permission { + constructor() { + this.mysql = Mysql + } + + async create(args) { + if (args.id) { + const g = await this.get({ id: args.id }) + if (g.length) return g[0].id + } + + return await Mysql.insert( + `INSERT INTO nt_perm`, + mapToDbColumn(args, permDbMap), + ) + } + + async get(args) { + const rows = await Mysql.select( + `SELECT nt_perm_id AS id + , nt_user_id AS uid + , nt_group_id AS gid + , inherit_perm AS inherit + , perm_name AS name + ${getPermFields()} + , deleted + FROM nt_perm WHERE`, + mapToDbColumn(args, permDbMap), + ) + for (const r of rows) { + for (const b of boolFields) { + r[b] = r[b] === 1 + } + } + return rows + } + + async put(args) { + if (!args.id) return false + const id = args.id + delete args.id + // Mysql.debug(1) + const r = await Mysql.update( + `UPDATE nt_perm SET`, + `WHERE nt_perm_id=${id}`, + mapToDbColumn(args, permDbMap), + ) + return r.changedRows === 1 + } + + async delete(args, val) { + const g = await this.get(args) + if (g.length !== 1) return false + await Mysql.execute(`UPDATE nt_perm SET deleted=? WHERE nt_perm_id=?`, [ + val ?? 1, + g[0].id, + ]) + return true + } + + async destroy(args) { + const g = await this.get(args) + if (g.length === 1) { + await Mysql.delete( + `DELETE FROM nt_perm WHERE`, + mapToDbColumn(args, permDbMap), + ) + } + } +} + +export default new Permission() + +function getPermFields() { + return ( + `, nt_perm.` + + [ + 'group_write', + 'group_create', + 'group_delete', + + 'zone_write', + 'zone_create', + 'zone_delegate', + 'zone_delete', + + 'zonerecord_write', + 'zonerecord_create', + 'zonerecord_delegate', + 'zonerecord_delete', + + 'user_write', + 'user_create', + 'user_delete', + + 'nameserver_write', + 'nameserver_create', + 'nameserver_delete', + + 'self_write', + 'usable_ns', + ].join(`, nt_perm.`) + ) +} diff --git a/lib/permission.test.js b/lib/permission.test.js new file mode 100644 index 0000000..bdc6f41 --- /dev/null +++ b/lib/permission.test.js @@ -0,0 +1,48 @@ +import assert from 'node:assert/strict' +import { describe, it, after } from 'node:test' + +import Permission from './permission.js' +import permTestCase from './test/permission.json' with { type: 'json' } + +after(async () => { + Permission.mysql.disconnect() +}) + +describe('permission', function () { + it('creates a permission', async () => { + assert.ok(await Permission.create(permTestCase)) + }) + + it('gets permission by id', async () => { + const g = await Permission.get({ id: permTestCase.id }) + assert.deepEqual(g[0], permTestCase) + }) + + it('gets permission by user id', async () => { + const g = await Permission.get({ uid: permTestCase.uid }) + assert.deepEqual(g[0], permTestCase) + }) + + it('gets permission by group id', async () => { + const g = await Permission.get({ uid: permTestCase.gid }) + assert.deepEqual(g[0], permTestCase) + }) + + it('changes a permission', async () => { + assert.ok(await Permission.put({ id: permTestCase.id, name: 'Changed' })) + const perms = await Permission.get({ id: permTestCase.id }) + assert.deepEqual(perms[0].name, 'Changed') + assert.ok( + await Permission.put({ id: permTestCase.id, name: 'Test Permission' }), + ) + }) + + it('deletes a permission', async () => { + assert.ok(await Permission.delete({ id: permTestCase.id })) + let u = await Permission.get({ id: permTestCase.id }) + assert.equal(u[0].deleted, true) + await Permission.delete({ id: permTestCase.id }, 0) // restore + u = await Permission.get({ id: permTestCase.id }) + assert.equal(u[0].deleted, false) + }) +}) diff --git a/lib/session.js b/lib/session.js index 963d9f1..51f0366 100644 --- a/lib/session.js +++ b/lib/session.js @@ -1,22 +1,32 @@ -const Mysql = require('./mysql') +import Mysql from './mysql.js' +import { mapToDbColumn } from './util.js' + +const sessionDbMap = { + id: 'nt_user_session_id', + uid: 'nt_user_id', + session: 'nt_user_session', +} class Session { - constructor() {} + constructor() { + this.mysql = Mysql + } async create(args) { const r = await this.get(args) - if (r) return r.nt_user_session_id + if (r) return r.id - const id = await Mysql.insert(`INSERT INTO nt_user_session`, { - nt_user_id: args.nt_user_id, - nt_user_session: args.nt_user_session, - last_access: parseInt(Date.now() / 1000, 10), - }) + const id = await Mysql.insert( + `INSERT INTO nt_user_session`, + mapToDbColumn(args, sessionDbMap), + ) return id } async get(args) { - let query = `SELECT s.* + let query = `SELECT s.nt_user_session_id AS id + , s.nt_user_id AS uid + , s.nt_user_session AS session FROM nt_user_session s LEFT JOIN nt_user u ON s.nt_user_id = u.nt_user_id WHERE u.deleted=0` @@ -28,19 +38,24 @@ class Session { params.push(args[f]) } } + for (const g of ['id', 'uid', 'session']) { + if (args[g] !== undefined) { + query += ` AND s.${sessionDbMap[g]} = ?` + params.push(args[g]) + } + } const sessions = await Mysql.execute(query, params) return sessions[0] } async delete(args) { - const r = await Mysql.execute( - `DELETE FROM nt_user_session WHERE nt_user_session_id=?`, - [args.nt_user_session_id], + const r = await Mysql.select( + `DELETE FROM nt_user_session WHERE`, + mapToDbColumn(args, sessionDbMap), ) return r.affectedRows === 1 } } -module.exports = new Session() -module.exports._mysql = Mysql +export default new Session() diff --git a/lib/session.test.js b/lib/session.test.js index 53729d0..904fb31 100644 --- a/lib/session.test.js +++ b/lib/session.test.js @@ -1,49 +1,58 @@ -const assert = require('node:assert/strict') -const { describe, it, after } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it, after, before } from 'node:test' -const session = require('./session') -const userCase = require('../test/v3/user.json') +import User from './user.js' +import Session from './session.js' +import userCase from './test/user.json' with { type: 'json' } + +before(async () => { + await User.create(userCase) +}) after(async () => { - session._mysql.disconnect() + await User.mysql.disconnect() }) describe('session', function () { - // session._mysql.debug(true) + // Session._mysql.debug(true) let sessionId describe('create', () => { it('creates a login session', async () => { - sessionId = await session.create({ + sessionId = await Session.create({ nt_user_id: userCase.id, - nt_user_session: '3.0.0', + session: '3.0.0', + last_access: parseInt(Date.now() / 1000, 10), }) assert.ok(sessionId) }) }) describe('get', () => { - it('finds a session by ID', async () => { - const s = await session.get({ nt_user_session_id: sessionId }) - assert.ok(s.nt_user_session_id) + it('finds a session by id', async () => { + const s = await Session.get({ id: sessionId }) + // console.log(s) + assert.ok(s?.id) + }) + + it('finds a session by nt_user_session_id', async () => { + const s = await Session.get({ nt_user_session_id: sessionId }) + assert.ok(s?.id) }) it('finds a session by session', async () => { - const s = await session.get({ nt_user_session: '3.0.0' }) - assert.ok(s.nt_user_session_id) + const s = await Session.get({ nt_user_session: '3.0.0' }) + assert.ok(s?.id) }) }) describe('delete', () => { it('deletes a session by ID', async () => { - assert.ok(await session.delete({ nt_user_session_id: sessionId })) + assert.ok(await Session.delete({ id: sessionId })) }) it('does not find a deleted session', async () => { - assert.equal( - await session.get({ nt_user_session_id: sessionId }), - undefined, - ) + assert.equal(await Session.get({ id: sessionId }), undefined) }) }) }) diff --git a/test/v3/group.json b/lib/test/group.json similarity index 100% rename from test/v3/group.json rename to lib/test/group.json diff --git a/lib/test/permission.json b/lib/test/permission.json new file mode 100644 index 0000000..aa4a736 --- /dev/null +++ b/lib/test/permission.json @@ -0,0 +1,27 @@ +{ + "id": 4096, + "uid": 4096, + "gid": 4096, + "inherit": true, + "name": "Test Permission", + "group_write": false, + "group_create": false, + "group_delete": false, + "zone_write": true, + "zone_create": true, + "zone_delegate": true, + "zone_delete": true, + "zonerecord_write": false, + "zonerecord_create": false, + "zonerecord_delegate": false, + "zonerecord_delete": false, + "user_write": false, + "user_create": false, + "user_delete": false, + "nameserver_write": false, + "nameserver_create": false, + "nameserver_delete": false, + "self_write": false, + "usable_ns": "", + "deleted": false +} diff --git a/test/v3/user.json b/lib/test/user.json similarity index 80% rename from test/v3/user.json rename to lib/test/user.json index 32b9a59..9bb6989 100644 --- a/test/v3/user.json +++ b/lib/test/user.json @@ -3,7 +3,7 @@ "gid": 4096, "username": "unit-test", "email": "unit-test@example.com", - "password": "What@%Fun34Security", + "password": "Wh@tA-Decent#P6ssw0rd", "first_name": "Unit", "last_name": "Test", "deleted": false diff --git a/lib/user.js b/lib/user.js index d0c7b3f..11340e3 100644 --- a/lib/user.js +++ b/lib/user.js @@ -1,16 +1,16 @@ -const crypto = require('node:crypto') +import crypto from 'node:crypto' -const Mysql = require('./mysql') -const Util = require('./util') -Util.setEnv() -const Config = require('./config') +import Mysql from './mysql.js' +import Config from './config.js' +import { mapToDbColumn } from './util.js' const userDbMap = { id: 'nt_user_id', gid: 'nt_group_id' } class User { - constructor(args) { + constructor(args = {}) { this.debug = args?.debug ?? false this.cfg = Config.getSync('session') + this.mysql = Mysql } async authenticate(authTry) { @@ -62,7 +62,6 @@ class User { } async create(args) { - // console.log(args) const u = await this.get({ id: args.id, gid: args.gid }) if (u.length === 1) return u[0].id @@ -73,7 +72,7 @@ class User { const userId = await Mysql.insert( `INSERT INTO nt_user`, - Util.mapToDbColumn(args, userDbMap), + mapToDbColumn(args, userDbMap), ) return userId } @@ -88,7 +87,7 @@ class User { , username , email FROM nt_user WHERE`, - Util.mapToDbColumn(args, userDbMap), + mapToDbColumn(args, userDbMap), ) } @@ -104,37 +103,42 @@ class User { , email , deleted FROM nt_user WHERE`, - Util.mapToDbColumn(args, userDbMap), + mapToDbColumn(args, userDbMap), ) } + async put(args) { + if (!args.id) return false + const id = args.id + delete args.id + const r = await Mysql.update( + `UPDATE nt_user SET`, + `WHERE nt_user_id=${id}`, + mapToDbColumn(args, userDbMap), + ) + return r.changedRows === 1 + } + async delete(args, val) { const u = await this.getAdmin(args) if (u.length !== 1) return false - await Mysql.execute(`UPDATE nt_user SET deleted=? WHERE nt_user_id=?`, [ - val ?? 1, - u[0].id, - ]) - return true + const r = await Mysql.execute( + `UPDATE nt_user SET deleted=? WHERE nt_user_id=?`, + [val ?? 1, u[0].id], + ) + return r.changedRows === 1 } async destroy(args) { const u = await this.getAdmin(args) if (u.length === 1) { - await Mysql.execute(`DELETE FROM nt_user WHERE nt_user_id=?`, [u[0].id]) + await Mysql.delete( + `DELETE FROM nt_user WHERE`, + mapToDbColumn({ id: u[0].id }, userDbMap), + ) } } - // async get_perms(user_id) { - // return await Mysql.execute( - // ` - // SELECT ${getPermFields()} FROM nt_perm - // WHERE deleted=0 - // AND nt_user_id = ?`, - // [user_id], - // ) - // } - generateSalt(length = 16) { const chars = Array.from({ length: 87 }, (_, i) => String.fromCharCode(i + 40), @@ -179,39 +183,4 @@ class User { } } -module.exports = new User() -module.exports._mysql = Mysql - -/* -function getPermFields() { - return ( - `nt_perm.` + - [ - 'group_write', - 'group_create', - 'group_delete', - - 'zone_write', - 'zone_create', - 'zone_delegate', - 'zone_delete', - - 'zonerecord_write', - 'zonerecord_create', - 'zonerecord_delegate', - 'zonerecord_delete', - - 'user_write', - 'user_create', - 'user_delete', - - 'nameserver_write', - 'nameserver_create', - 'nameserver_delete', - - 'self_write', - 'usable_ns', - ].join(`, nt_perm.`) - ) -} -*/ +export default new User() diff --git a/lib/user.test.js b/lib/user.test.js index 9e927e2..6a77c30 100644 --- a/lib/user.test.js +++ b/lib/user.test.js @@ -1,87 +1,78 @@ -const assert = require('node:assert/strict') -const { describe, it, after } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it, after, before } from 'node:test' -const user = require('./user') +import User from './user.js' +import Group from './group.js' + +import testCase from './test/user.json' with { type: 'json' } +import groupCase from './test/group.json' with { type: 'json' } + +before(async () => { + await Group.create(groupCase) +}) after(async () => { - user._mysql.disconnect() + User.mysql.disconnect() }) +function sanitize(u) { + const r = JSON.parse(JSON.stringify(u)) + for (const f of ['deleted', 'password', 'pass_salt']) { + delete r[f] + } + return r +} + describe('user', function () { - describe('get', function () { + describe('POST', function () { + it('creates a user', async () => { + assert.ok(await User.create(testCase)) + let users = await User.get({ id: testCase.id }) + assert.deepEqual(users[0], sanitize(testCase)) + }) + }) + + describe('GET', function () { it('finds existing user by id', async () => { - const u = await user.get({ id: 4096 }) + const u = await User.get({ id: testCase.id }) // console.log(u) - assert.deepEqual(u[0], { - gid: 4096, - id: 4096, - username: 'unit-test', - email: 'unit-test@example.com', - first_name: 'Unit', - last_name: 'Test', - // deleted: 0, - }) + assert.deepEqual(u[0], sanitize(testCase)) }) it('finds existing user by username', async () => { - const u = await user.get({ username: 'unit-test' }) - // console.log(u) - assert.deepEqual(u[0], { - gid: 4096, - id: 4096, - username: 'unit-test', - email: 'unit-test@example.com', - first_name: 'Unit', - last_name: 'Test', - // deleted: 0, - }) + const u = await User.get({ username: 'unit-test' }) + assert.deepEqual(u[0], sanitize(testCase)) }) + }) - it('deletes a user', async () => { - assert.ok(await user.delete({ id: 4096 })) - let u = await user.getAdmin({ id: 4096 }) - assert.equal(u[0].deleted, 1) - await user.delete({ id: 4096 }, 0) // restore - u = await user.getAdmin({ id: 4096 }) - assert.equal(u[0].deleted, 0) + describe('PUT', function () { + it('modifies existing user', async () => { + assert.ok(await User.put({ id: testCase.id, first_name: 'Untie' })) + let users = await User.get({ id: testCase.id }) + assert.equal(users[0].first_name, 'Untie') + await User.put({ id: testCase.id, first_name: 'Unit' }) }) }) - describe('get_perms', function () { - it.skip('gets user permissions', async () => { - const p = await user.get_perms(242) - assert.deepEqual(p[0], { - group_create: 1, - group_delete: 1, - group_write: 1, - nameserver_create: 0, - nameserver_delete: 0, - nameserver_write: 0, - self_write: 1, - usable_ns: null, - user_create: 1, - user_delete: 1, - user_write: 1, - zone_create: 1, - zone_delegate: 1, - zone_delete: 1, - zone_write: 1, - zonerecord_create: 1, - zonerecord_delegate: 1, - zonerecord_delete: 1, - zonerecord_write: 1, - }) + describe('DELETE', function () { + it('deletes a user', async () => { + assert.ok(await User.delete({ id: testCase.id })) + let u = await User.getAdmin({ id: testCase.id }) + assert.equal(u[0].deleted, 1) + await User.delete({ id: testCase.id }, 0) // restore + u = await User.getAdmin({ id: testCase.id }) + assert.equal(u[0].deleted, 0) }) }) describe('validPassword', function () { it('auths user with plain text password', async () => { - const r = await user.validPassword('test', 'test', 'demo', '') + const r = await User.validPassword('test', 'test', 'demo', '') assert.equal(r, true) }) it('auths valid pbkdb2 password', async () => { - const r = await user.validPassword( + const r = await User.validPassword( 'YouGuessedIt!', '050cfa70c3582be0d5bfae25138a8486dc2e6790f39bc0c4e111223ba6034432', 'unit-test', @@ -91,7 +82,7 @@ describe('user', function () { }) it('rejects invalid pbkdb2 password', async () => { - const r = await user.validPassword( + const r = await User.validPassword( 'YouMissedIt!', '050cfa70c3582be0d5bfae25138a8486dc2e6790f39bc0c4e111223ba6034432', 'unit-test', @@ -101,7 +92,7 @@ describe('user', function () { }) it('auths valid SHA1 password', async () => { - const r = await user.validPassword( + const r = await User.validPassword( 'OhNoYouDont', '083007777a5241d01abba70c938c60d80be60027', 'unit-test', @@ -110,7 +101,7 @@ describe('user', function () { }) it('rejects invalid SHA1 password', async () => { - const r = await user.validPassword( + const r = await User.validPassword( 'OhNoYouDont', '083007777a5241d01abba7Oc938c60d80be60027', 'unit-test', @@ -121,7 +112,7 @@ describe('user', function () { describe('authenticate', () => { it('rejects invalid user', async () => { - const u = await user.authenticate({ + const u = await User.authenticate({ username: 'fake-test@example.com', password: 'evilCrackerJack', }) @@ -129,7 +120,7 @@ describe('user', function () { }) it('rejects invalid pass', async () => { - const u = await user.authenticate({ + const u = await User.authenticate({ username: 'unit-test@example.com', password: 'evilCrackerJack', }) @@ -137,7 +128,7 @@ describe('user', function () { }) it('accepts a valid username & password', async () => { - const u = await user.authenticate({ + const u = await User.authenticate({ username: 'unit-test@example.com', password: 'Wh@tA-Decent#P6ssw0rd', }) diff --git a/lib/util.js b/lib/util.js index e78b43f..38347d9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,8 +1,12 @@ -exports.setEnv = () => { +import os from 'node:os' + +import pkgJson from '../package.json' with { type: 'json' } + +function setEnv() { if (process.env.NODE_ENV !== undefined) return /* c8 ignore next 9 */ - switch (require('os').hostname()) { + switch (os.hostname()) { case 'mbp.simerson.net': case 'imac27.simerson.net': process.env.NODE_ENV = 'development' @@ -13,13 +17,13 @@ exports.setEnv = () => { console.log(`NODE_ENV: ${process.env.NODE_ENV}`) } -exports.meta = { +const meta = { api: { - version: require('../package.json').version, + version: pkgJson.version, }, } -exports.mapToDbColumn = function (args, maps) { +function mapToDbColumn(args, maps) { // create an instance, so we don't mangle the original const newArgs = JSON.parse(JSON.stringify(args)) @@ -31,3 +35,5 @@ exports.mapToDbColumn = function (args, maps) { } return newArgs } + +export { setEnv, meta, mapToDbColumn } diff --git a/lib/util.test.js b/lib/util.test.js index b853988..40ad143 100644 --- a/lib/util.test.js +++ b/lib/util.test.js @@ -1,13 +1,13 @@ -const assert = require('node:assert/strict') -const { describe, it } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' -const util = require('./util') +import { setEnv, mapToDbColumn, meta } from './util.js' describe('util', function () { if (process.env.NODE_ENV === undefined) { describe('setEnv', function () { it('sets process.env.NODE_ENV', async () => { - util.setEnv() + setEnv() assert.ok(process.env.NODE_ENV) }) }) @@ -15,7 +15,7 @@ describe('util', function () { describe('meta', () => { it('returns the package version', () => { - assert.deepEqual(util.meta, { api: { version: '3.0.0' } }) + assert.ok(/3.0.0/.test(meta.api.version)) }) }) @@ -23,7 +23,7 @@ describe('util', function () { it('maps short names to DB fields', async () => { const before = { id: 5 } const mappings = { id: 'nt_user_id' } - assert.deepEqual(util.mapToDbColumn(before, mappings), { nt_user_id: 5 }) + assert.deepEqual(mapToDbColumn(before, mappings), { nt_user_id: 5 }) }) }) }) diff --git a/package.json b/package.json index 54b3d28..daf5eab 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "@nictool/api", - "version": "3.0.0", + "version": "3.0.0-alpha.0", "description": "NicTool API", "main": "index.js", + "type": "module", "scripts": { "format": "npm run lint:fix && npm run prettier:fix", "lint": "npx eslint *.js **/*.js", @@ -11,9 +12,9 @@ "prettier:fix": "npm run prettier -- --write", "start": "NODE_ENV=production node ./server", "develop": "NODE_ENV=development node ./server", - "test": "test/run.sh", + "test": "./test.sh", "versions": "npx dependency-version-checker check", - "watch": "node test/suite setup && node --test --watch; node test/suite teardown" + "watch": "./test.sh watch" }, "repository": { "type": "git", @@ -32,6 +33,8 @@ }, "homepage": "https://github.com/NicTool/api#readme", "devDependencies": { + "@babel/eslint-parser": "^7.23.10", + "@babel/plugin-syntax-import-attributes": "^7.23.3", "eslint": "^8.57.0" }, "dependencies": { @@ -40,10 +43,10 @@ "@hapi/hoek": "^11.0.4", "@hapi/inert": "^7.1.0", "@hapi/vision": "^7.0.3", - "@nictool/validate": "^0.7.1", + "@nictool/validate": "^0.7.2", "hapi-swagger": "^17.2.1", - "mysql2": "^3.9.1", + "mysql2": "^3.9.2", "qs": "^6.11.2", - "yaml": "^2.3.4" + "yaml": "^2.4.0" } } diff --git a/routes/group.js b/routes/group.js index 966a9e0..72261d5 100644 --- a/routes/group.js +++ b/routes/group.js @@ -1,9 +1,9 @@ -const validate = require('@nictool/validate') +import validate from '@nictool/validate' -const Group = require('../lib/group') -const Util = require('../lib/util') +import Group from '../lib/group.js' +import { meta } from '../lib/util.js' -module.exports = (server) => { +function GroupRoutes(server) { server.route([ { method: 'GET', @@ -23,7 +23,7 @@ module.exports = (server) => { return h .response({ meta: { - api: Util.meta.api, + api: meta.api, msg: `No unique group match`, }, }) @@ -34,7 +34,7 @@ module.exports = (server) => { .response({ group: groups[0], meta: { - api: Util.meta.api, + api: meta.api, msg: `here's your group`, }, }) @@ -54,7 +54,6 @@ module.exports = (server) => { tags: ['api'], }, handler: async (request, h) => { - // console.log(request.payload) const gid = await Group.create(request.payload) if (!gid) { console.log(`POST /group oops`) // TODO @@ -66,7 +65,7 @@ module.exports = (server) => { .response({ group: groups[0], meta: { - api: Util.meta.api, + api: meta.api, msg: `I created this group`, }, }) @@ -88,21 +87,22 @@ module.exports = (server) => { return h .response({ meta: { - api: Util.meta.api, + api: meta.api, msg: `No unique group match`, }, }) .code(204) } - await Group.delete({ id: groups[0].id }) + const action = request.query.destroy === 'true' ? 'destroy' : 'delete' + await Group[action]({ id: groups[0].id }) delete groups[0].gid return h .response({ group: groups[0], meta: { - api: Util.meta.api, + api: meta.api, msg: `I deleted that group`, }, }) @@ -111,3 +111,5 @@ module.exports = (server) => { }, ]) } + +export default GroupRoutes diff --git a/routes/group.test.js b/routes/group.test.js index 70693c7..d2668be 100644 --- a/routes/group.test.js +++ b/routes/group.test.js @@ -1,64 +1,67 @@ -const assert = require('node:assert/strict') -const { describe, it, before, after } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it, before, after } from 'node:test' -const { init } = require('./index') -const Group = require('../lib/group') -const groupCase = require('../test/v3/group.json') +import { init } from './index.js' +import Group from '../lib/group.js' +import User from '../lib/user.js' + +import groupCase from './test/group.json' with { type: 'json' } +import userCase from './test/user.json' with { type: 'json' } + +let server before(async () => { - const userCase = require('../test/v3/user.json') - this.server = await init() - const res = await this.server.inject({ - method: 'POST', - url: '/session', - payload: { - username: `${userCase.username}@example.com`, - password: 'Wh@tA-Decent#P6ssw0rd', - }, - }) - assert.ok(res.headers['set-cookie'][0]) - this.sessionCookie = res.headers['set-cookie'][0].split(';')[0] + server = await init() + await Group.create(groupCase) + await User.create(userCase) }) after(async () => { - const res = await this.server.inject({ - method: 'DELETE', - url: '/session', - headers: { - Cookie: this.sessionCookie, - }, - }) - // console.log(res.result) - assert.equal(res.statusCode, 200) - - await this.server.stop() + await server.stop() }) -describe('group', () => { - it('GET /group/4096', async () => { - const res = await this.server.inject({ +describe('group routes', () => { + let sessionCookie + + it('POST /session establishes a session', async () => { + const res = await server.inject({ + method: 'POST', + url: '/session', + payload: { + username: `${userCase.username}@${groupCase.name}`, + password: userCase.password, + }, + }) + assert.ok(res.headers['set-cookie'][0]) + sessionCookie = res.headers['set-cookie'][0].split(';')[0] + }) + + it(`GET /group/${groupCase.id}`, async () => { + const res = await server.inject({ method: 'GET', - url: '/group/4096', + url: `/group/${groupCase.id}`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) + const case2Id = 4094 + it('POST /group', async () => { const testCase = JSON.parse(JSON.stringify(groupCase)) - testCase.id = 4095 // make it unique + testCase.id = case2Id // make it unique testCase.name = `example2.com` delete testCase.deleted // console.log(testCase) - const res = await this.server.inject({ + const res = await server.inject({ method: 'POST', url: '/group', headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, payload: testCase, }) @@ -67,54 +70,73 @@ describe('group', () => { }) it('GET /group', async () => { - const res = await this.server.inject({ + const res = await server.inject({ method: 'GET', - url: '/group/4095', + url: `/group/${case2Id}`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) - it('DELETE /group', async () => { - const res = await this.server.inject({ + it(`DELETE /group/${case2Id}`, async () => { + const res = await server.inject({ method: 'DELETE', - url: '/group/4095', + url: `/group/${case2Id}`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) - it('GET /group/4095', async () => { - const res = await this.server.inject({ + it(`GET /group/${case2Id}`, async () => { + const res = await server.inject({ method: 'GET', - url: '/group/4095', + url: `/group/${case2Id}`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 204) }) - it('GET /group/4095 (deleted)', async () => { - const res = await this.server.inject({ + it(`GET /group/${case2Id} (deleted)`, async () => { + const res = await server.inject({ method: 'GET', - url: '/group/4095?deleted=1', + url: `/group/${case2Id}?deleted=1`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) - it('nukes /group/4095', async () => { - assert.ok(Group.destroy({ id: 4095 })) + it(`DELETE /group/${case2Id}`, async () => { + const res = await server.inject({ + method: 'DELETE', + url: `/group/${case2Id}?destroy=true`, + headers: { + Cookie: sessionCookie, + }, + }) + // console.log(res.result) + assert.equal(res.statusCode, 200) + }) + + it('DELETE /session', async () => { + const res = await server.inject({ + method: 'DELETE', + url: '/session', + headers: { + Cookie: sessionCookie, + }, + }) + assert.equal(res.statusCode, 200) }) }) diff --git a/routes/index.js b/routes/index.js index 382d371..3c3d213 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,25 +1,28 @@ 'use strict' -const path = require('node:path') +import path from 'node:path' +import url from 'node:url' -const Hapi = require('@hapi/hapi') -const Inert = require('@hapi/inert') -const Vision = require('@hapi/vision') -const HapiSwagger = require('hapi-swagger') +import Hapi from '@hapi/hapi' +import Cookie from '@hapi/cookie' +import Inert from '@hapi/inert' +import Vision from '@hapi/vision' +import HapiSwagger from 'hapi-swagger' +// import Hoek from '@hapi/hoek' -const qs = require('qs') -// const hoek = require('@hapi/hoek') -// const validate = require('@nictool/validate') +import qs from 'qs' -const util = require('../lib/util') -util.setEnv() -const Config = require('../lib/config') -const Session = require('../lib/session') -const User = require('../lib/user') +import Config from '../lib/config.js' + +import pkgJson from '../package.json' with { type: 'json' } + +import GroupRoutes from './group.js' +import { User, UserRoutes } from './user.js' +import { Session, SessionRoutes } from './session.js' let server -const setup = async () => { +async function setup() { const httpCfg = await Config.get('http') server = Hapi.server({ @@ -30,13 +33,16 @@ const setup = async () => { }, routes: { files: { - relativeTo: path.join(__dirname, 'html'), + relativeTo: path.join( + path.dirname(url.fileURLToPath(import.meta.url)), + 'html', + ), }, }, }) - await server.register(require('@hapi/cookie')) - await server.register(require('@hapi/inert')) + await server.register(Cookie) + await server.register(Inert) await server.register([ Inert, Vision, @@ -45,7 +51,7 @@ const setup = async () => { options: { info: { title: 'NicTool API Documentation', - version: require('../package.json').version, + version: pkgJson.version, }, }, }, @@ -57,10 +63,10 @@ const setup = async () => { cookie: sessionCfg.cookie, validate: async (request, session) => { - const s = await Session.get({ nt_user_session_id: session.session_id }) + const s = await Session.get({ id: session.nt_user_session_id }) if (!s) return { isValid: false } // invalid cookie - // const account = await User.get({ nt_user_id: s.nt_user_id }) + // const account = await User.get({ id: s.nt_user_id }) return { isValid: true } // , credentials: account } }, }) @@ -75,9 +81,9 @@ const setup = async () => { }, }) - require('./group')(server) - require('./user')(server) - require('./session')(server) + GroupRoutes(server) + UserRoutes(server) + SessionRoutes(server) server.route({ method: '*', @@ -88,23 +94,27 @@ const setup = async () => { }) server.events.on('stop', () => { - User._mysql.disconnect() + if (User.mysql) User.mysql.disconnect() + if (Session.mysql) Session.mysql.disconnect() }) } -exports.init = async () => { +async function init() { await setup() await server.initialize() return server } -exports.start = async () => { +async function start() { await setup() + /* c8 ignore next 3 */ await server.start() console.log(`Server running at: ${server.info.uri}`) return server } +export { init, start } + process.on('unhandledRejection', (err) => { console.error(err) process.exit(1) diff --git a/routes/session.js b/routes/session.js index c6d179b..6030a8f 100644 --- a/routes/session.js +++ b/routes/session.js @@ -1,11 +1,11 @@ -const validate = require('@nictool/validate') +import validate from '@nictool/validate' -const Group = require('../lib/group') -const User = require('../lib/user') -const Session = require('../lib/session') -const Util = require('../lib/util') +import Group from '../lib/group.js' +import User from '../lib/user.js' +import Session from '../lib/session.js' +import { meta } from '../lib/util.js' -module.exports = (server) => { +function SessionRoutes(server) { server.route([ { method: 'GET', @@ -19,7 +19,7 @@ module.exports = (server) => { handler: async (request, h) => { const { nt_user_id, nt_user_session_id } = request.state['sid-nictool'] const users = await User.get({ id: nt_user_id }) - const groups = await Group.get({ nt_group_id: users[0].gid }) + const groups = await Group.get({ id: users[0].gid }) delete users[0].gid return h .response({ @@ -27,7 +27,7 @@ module.exports = (server) => { group: groups[0], session: { id: nt_user_session_id }, meta: { - api: Util.meta.api, + api: meta.api, msg: `working on it`, }, }) @@ -56,6 +56,7 @@ module.exports = (server) => { const sessId = await Session.create({ nt_user_id: account.user.id, nt_user_session: '3.0.0', + last_access: parseInt(Date.now() / 1000, 10), }) request.cookieAuth.set({ @@ -69,7 +70,7 @@ module.exports = (server) => { group: account.group, session: { id: sessId }, meta: { - api: Util.meta.api, + api: meta.api, msg: `you are logged in`, }, }) @@ -85,7 +86,7 @@ module.exports = (server) => { return h .response({ meta: { - api: Util.meta.api, + api: meta.api, msg: 'You are logged out', }, }) @@ -100,3 +101,7 @@ module.exports = (server) => { }, ]) } + +export default SessionRoutes + +export { Session, SessionRoutes } diff --git a/routes/session.test.js b/routes/session.test.js index b0749ad..311ec69 100644 --- a/routes/session.test.js +++ b/routes/session.test.js @@ -1,30 +1,38 @@ -const assert = require('node:assert/strict') -const { describe, it, before, after } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it, before, after } from 'node:test' -const { init } = require('./index') -const userCase = require('../test/v3/user.json') +import { init } from './index.js' +import userCase from './test/user.json' with { type: 'json' } +import groupCase from './test/group.json' with { type: 'json' } + +import User from '../lib/user.js' +import Group from '../lib/group.js' + +let server before(async () => { - this.server = await init() + await Group.create(groupCase) + await User.create(userCase) + server = await init() }) after(async () => { - await this.server.stop() + await server.stop() }) const parseCookie = (c) => { return c.split(';')[0] } -describe('routes', () => { - const routes = [{ GET: '/' }, { GET: '/user' }, { DELETE: '/session' }] +describe('session routes', () => { + const routes = [{ GET: '/' }, { DELETE: '/session' }] describe('no session responds with 401', () => { for (const r of routes) { const key = Object.keys(r)[0] const val = Object.values(r)[0] it(`${key} ${val}`, async () => { - const res = await this.server.inject({ + const res = await server.inject({ method: key, url: val, }) @@ -34,64 +42,46 @@ describe('routes', () => { } }) - describe('valid auth sets a cookie', () => { - it('POST /session', async () => { - const res = await this.server.inject({ - method: 'POST', - url: '/session', - payload: { - username: `${userCase.username}@example.com`, - password: 'Wh@tA-Decent#P6ssw0rd', - }, - }) - assert.ok(res.headers['set-cookie'][0]) - this.sessionCookie = parseCookie(res.headers['set-cookie'][0]) - // console.log(res.result) - }) - }) - describe('with session, can retrieve private URIs', () => { + let sessionCookie + before(async () => { - const res = await this.server.inject({ + const res = await server.inject({ method: 'POST', url: '/session', payload: { - username: `${userCase.username}@example.com`, - password: 'Wh@tA-Decent#P6ssw0rd', + username: `${userCase.username}@${groupCase.name}`, + password: userCase.password, }, }) + assert.equal(res.statusCode, 200) assert.ok(res.headers['set-cookie'][0]) - this.sessionCookie = parseCookie(res.headers['set-cookie'][0]) + sessionCookie = parseCookie(res.headers['set-cookie'][0]) }) after(async () => { - const res = await this.server.inject({ + const res = await server.inject({ method: 'DELETE', url: '/session', headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) - const routes = [ - { GET: '/' }, - { GET: '/user' }, - { GET: '/session' }, - { DELETE: '/session' }, - ] + const routes = [{ GET: '/' }, { GET: '/session' }, { DELETE: '/session' }] for (const r of routes) { const key = Object.keys(r)[0] const val = Object.values(r)[0] it(`${key} ${val}`, async () => { - const res = await this.server.inject({ + const res = await server.inject({ method: key, url: val, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) assert.equal(res.request.auth.isAuthenticated, true) diff --git a/routes/test/group.json b/routes/test/group.json new file mode 100644 index 0000000..1cafca0 --- /dev/null +++ b/routes/test/group.json @@ -0,0 +1,6 @@ +{ + "id": 4095, + "parent_gid": 0, + "name": "route.example.com", + "deleted": false +} diff --git a/routes/test/permission.json b/routes/test/permission.json new file mode 100644 index 0000000..52db656 --- /dev/null +++ b/routes/test/permission.json @@ -0,0 +1,27 @@ +{ + "id": 4095, + "uid": 4095, + "gid": 4095, + "inherit": true, + "name": "Route Test Permission", + "group_write": false, + "group_create": false, + "group_delete": false, + "zone_write": true, + "zone_create": true, + "zone_delegate": true, + "zone_delete": true, + "zonerecord_write": false, + "zonerecord_create": false, + "zonerecord_delegate": false, + "zonerecord_delete": false, + "user_write": false, + "user_create": false, + "user_delete": false, + "nameserver_write": false, + "nameserver_create": false, + "nameserver_delete": false, + "self_write": false, + "usable_ns": "", + "deleted": false +} diff --git a/routes/test/user.json b/routes/test/user.json new file mode 100644 index 0000000..230db51 --- /dev/null +++ b/routes/test/user.json @@ -0,0 +1,10 @@ +{ + "id": 4095, + "gid": 4095, + "username": "route-test", + "email": "route-test@example.com", + "password": "Wh@tA-Decent#P6ssw0rd", + "first_name": "Route", + "last_name": "Test", + "deleted": false +} diff --git a/routes/user.js b/routes/user.js index 1784bd1..778139c 100644 --- a/routes/user.js +++ b/routes/user.js @@ -1,9 +1,9 @@ -const validate = require('@nictool/validate') +import validate from '@nictool/validate' -const User = require('../lib/user') -const Util = require('../lib/util') +import User from '../lib/user.js' +import { meta } from '../lib/util.js' -module.exports = (server) => { +function UserRoutes(server) { server.route([ { method: 'GET', @@ -24,7 +24,7 @@ module.exports = (server) => { .response({ user: users[0], meta: { - api: Util.meta.api, + api: meta.api, msg: `this is you`, }, }) @@ -49,7 +49,7 @@ module.exports = (server) => { return h .response({ meta: { - api: Util.meta.api, + api: meta.api, msg: `No unique user match`, }, }) @@ -64,7 +64,7 @@ module.exports = (server) => { user: users[0], group: { id: gid }, meta: { - api: Util.meta.api, + api: meta.api, msg: `here's your user`, }, }) @@ -84,7 +84,6 @@ module.exports = (server) => { tags: ['api'], }, handler: async (request, h) => { - // console.log(request.payload) const uid = await User.create(request.payload) if (!uid) { console.log(`POST /user oops`) // TODO @@ -99,7 +98,7 @@ module.exports = (server) => { user: users[0], group, meta: { - api: Util.meta.api, + api: meta.api, msg: `I created this user`, }, }) @@ -121,21 +120,23 @@ module.exports = (server) => { return h .response({ meta: { - api: Util.meta.api, + api: meta.api, msg: `No unique user match`, }, }) .code(204) } - await User.delete({ id: users[0].id }) + const action = request.query.destroy === 'true' ? 'destroy' : 'delete' + await User[action]({ id: users[0].id }) + delete users[0].gid return h .response({ user: users[0], meta: { - api: Util.meta.api, + api: meta.api, msg: `I deleted that user`, }, }) @@ -144,3 +145,7 @@ module.exports = (server) => { }, ]) } + +export default UserRoutes + +export { User, UserRoutes } diff --git a/routes/user.test.js b/routes/user.test.js index 90380d5..2d5b298 100644 --- a/routes/user.test.js +++ b/routes/user.test.js @@ -1,78 +1,80 @@ -const assert = require('node:assert/strict') -const { describe, it, before, after } = require('node:test') +import assert from 'node:assert/strict' +import { describe, it, before, after } from 'node:test' -const { init } = require('./index') -const User = require('../lib/user') -const userCase = require('../test/v3/user.json') +import { init } from './index.js' +import User from '../lib/user.js' +import Group from '../lib/group.js' + +import groupCase from './test/group.json' with { type: 'json' } +import userCase from './test/user.json' with { type: 'json' } + +let server, sessionCookie before(async () => { - this.server = await init() - const res = await this.server.inject({ - method: 'POST', - url: '/session', - payload: { - username: `${userCase.username}@example.com`, - password: 'Wh@tA-Decent#P6ssw0rd', - }, - }) - assert.ok(res.headers['set-cookie'][0]) - this.sessionCookie = parseCookie(res.headers['set-cookie'][0]) + server = await init() + await Group.create(groupCase) + await User.create(userCase) }) after(async () => { - const res = await this.server.inject({ - method: 'DELETE', - url: '/session', - headers: { - Cookie: this.sessionCookie, - }, - }) - // console.log(res.result) - assert.equal(res.statusCode, 200) - - await this.server.stop() + await server.stop() }) const parseCookie = (c) => { return c.split(';')[0] } -describe('user', () => { +describe('user routes', () => { + it('POST /session establishes a session', async () => { + const res = await server.inject({ + method: 'POST', + url: '/session', + payload: { + username: `${userCase.username}@${groupCase.name}`, + password: userCase.password, + }, + }) + assert.ok(res.headers['set-cookie'][0]) + sessionCookie = parseCookie(res.headers['set-cookie'][0]) + }) + it('GET /user', async () => { - const res = await this.server.inject({ + const res = await server.inject({ method: 'GET', url: '/user', headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) - it('GET /user/4096', async () => { - const res = await this.server.inject({ + it(`GET /user/${userCase.id}`, async () => { + const res = await server.inject({ method: 'GET', - url: '/user/4096', + url: `/user/${userCase.id}`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) + const userId2 = 4094 + it('POST /user', async () => { const testCase = JSON.parse(JSON.stringify(userCase)) - testCase.id = 4095 // make it unique + testCase.id = userId2 // make it unique testCase.username = `${testCase.username}2` delete testCase.deleted - const res = await this.server.inject({ + const res = await server.inject({ method: 'POST', url: '/user', headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, payload: testCase, }) @@ -80,55 +82,75 @@ describe('user', () => { assert.equal(res.statusCode, 201) }) - it('GET /user', async () => { - const res = await this.server.inject({ + it(`GET /user/${userId2}`, async () => { + const res = await server.inject({ method: 'GET', - url: '/user/4095', + url: `/user/${userId2}`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) - it('DELETE /user', async () => { - const res = await this.server.inject({ + it(`DELETE /user/${userId2}`, async () => { + const res = await server.inject({ method: 'DELETE', - url: '/user/4095', + url: `/user/${userId2}`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) - it('GET /user/4095', async () => { - const res = await this.server.inject({ + it(`GET /user/${userId2}`, async () => { + const res = await server.inject({ method: 'GET', - url: '/user/4095', + url: `/user/${userId2}`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 204) }) - it('GET /user/4095 (deleted)', async () => { - const res = await this.server.inject({ + it(`GET /user/${userId2}?deleted=1`, async () => { + const res = await server.inject({ method: 'GET', - url: '/user/4095?deleted=1', + url: `/user/${userId2}?deleted=1`, + headers: { + Cookie: sessionCookie, + }, + }) + // console.log(res.result) + assert.equal(res.statusCode, 200) + }) + + it(`DELETE /user/${userId2}`, async () => { + const res = await server.inject({ + method: 'DELETE', + url: `/user/${userId2}?destroy=true`, headers: { - Cookie: this.sessionCookie, + Cookie: sessionCookie, }, }) // console.log(res.result) assert.equal(res.statusCode, 200) }) - it('nukes /user/4095', async () => { - assert.ok(User.destroy({ id: 4095 })) + it('DELETE /session', async () => { + const res = await server.inject({ + method: 'DELETE', + url: '/session', + headers: { + Cookie: sessionCookie, + }, + }) + // console.log(res.result) + assert.equal(res.statusCode, 200) }) }) diff --git a/server.js b/server.js index f2b02f8..d8d9350 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,5 @@ 'use strict' -const { start } = require('./routes/index') +import { start } from './routes/index.js' start() diff --git a/sql/init-mysql.sh b/sql/init-mysql.sh index 3c3f454..e57572b 100755 --- a/sql/init-mysql.sh +++ b/sql/init-mysql.sh @@ -6,13 +6,14 @@ then # configure MySQL in the GitHub workflow runners case "$(uname -s)" in - Linux*) + Linux*) ;; - Darwin*) + Darwin*) mysqladmin --user=root --password='' --protocol=tcp password 'root' ;; - CYGWIN*|MINGW*|MINGW32*|MSYS*) - export MYSQL_PWD="" + CYGWIN*|MINGW*|MINGW32*|MSYS*) + mysqladmin --user=root --password='' --protocol=tcp password 'root' + # export MYSQL_PWD="" ;; esac fi diff --git a/test.js b/test.js new file mode 100644 index 0000000..6ff6df0 --- /dev/null +++ b/test.js @@ -0,0 +1,75 @@ +'use strict' + +import path from 'node:path' + +import Group from './lib/group.js' +import User from './lib/user.js' +import Session from './lib/session.js' + +import groupCase from './lib/test/group.json' with { type: 'json' } +import userCase from './lib/test/user.json' with { type: 'json' } +import groupCaseR from './routes/test/group.json' with { type: 'json' } +import userCaseR from './routes/test/user.json' with { type: 'json' } + +switch (process.argv[2]) { + case 'setup': + setup() + break + case 'teardown': + teardown() + break + default: + console.log( + `\nusage:\tnode ${path.basename(process.argv[1])} [ setup | teardown ]\n`, + ) +} + +async function setup() { + await createTestGroup() + await createTestUser() + // await createTestSession() + await User.mysql.disconnect() + await Group.mysql.disconnect() + process.exit(0) +} + +async function createTestGroup() { + await Group.create(groupCase) + await Group.create(groupCaseR) +} + +async function createTestUser() { + await User.create(userCase) + await User.create(userCaseR) +} + +// async function createTestSession() { +// await Session.create({ +// nt_user_id: userCase.nt_user_id, +// nt_user_session: '3.0.0', +// last_access: parseInt(Date.now(), 10), +// }) +// } + +async function teardown() { + await destroyTestSession() + await destroyTestUser() + await destroyTestGroup() + await User.mysql.disconnect() + await Group.mysql.disconnect() + process.exit(0) +} + +async function destroyTestGroup() { + await Group.destroy({ id: groupCase.id }) + await Group.destroy({ id: groupCaseR.id }) +} + +async function destroyTestUser() { + await User.destroy({ id: userCase.id }) + await User.destroy({ id: userCaseR.id }) +} + +async function destroyTestSession() { + await Session.delete({ nt_user_id: userCase.id }) +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..deb27f0 --- /dev/null +++ b/test.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +NODE="node --no-warnings=ExperimentalWarning" +$NODE test.js setup + +if [ "$1" = "watch" ]; then + $NODE --test --watch +else + # if [ -n "$GITHUB_WORKFLOW" ]; then + # npm i --no-save node-test-github-reporter + # $NODE --test --test-reporter=node-test-github-reporter + # else + $NODE --test --test-reporter=spec + # fi +fi + +# npx mocha --exit --no-warnings=ExperimentalWarning lib/*.test.js routes/*.test.js + +$NODE test.js teardown diff --git a/test/run.sh b/test/run.sh deleted file mode 100755 index 69abdad..0000000 --- a/test/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -node test/suite.js setup -node --test -node test/suite.js teardown diff --git a/test/suite.js b/test/suite.js deleted file mode 100644 index 45e2ffd..0000000 --- a/test/suite.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict' - -const path = require('node:path') - -const group = require('../lib/group') -const user = require('../lib/user') -// const session = require('../lib/session') -const userCase = require('./v3/user.json') -const groupCase = require('./v3/group.json') - -switch (process.argv[2]) { - case 'setup': - setup() - break - case 'teardown': - teardown() - break - default: - console.log( - `\nusage:\tnode ${path.basename(process.argv[1])} [ setup | teardown ]\n`, - ) -} - -async function setup() { - await createTestGroup() - await createTestUser() - // await createTestSession() - await user._mysql.disconnect() - await group._mysql.disconnect() - process.exit(0) -} - -async function createTestGroup() { - let g = group.get({ id: groupCase.id }) - if (g.length === 1) return - - await group.create(groupCase) -} - -async function createTestUser() { - let u = await user.get({ id: userCase.id }) - if (u.length === 1) return - - const instance = JSON.parse(JSON.stringify(userCase)) - instance.password = 'Wh@tA-Decent#P6ssw0rd' - - await user.create(instance) -} - -// async function createTestSession() { -// this.sessionId = await session.create({ -// nt_user_id: userCase.nt_user_id, -// nt_user_session: '3.0.0', -// }) -// } - -async function teardown() { - // await destroyTestSession() - await destroyTestUser() - await destroyTestGroup() - // await user._mysql.disconnect() - // await group._mysql.disconnect() - process.exit(0) -} - -async function destroyTestGroup() { - await group.destroy({ id: groupCase.id }) -} - -async function destroyTestUser() { - await user.destroy({ id: userCase.id }) -} - -// async function destroyTestSession() { -// await session.destroy({ nt_user_id: ... }) -// } diff --git a/test/v2/group.json b/test/v2/group.json deleted file mode 100644 index 41c8429..0000000 --- a/test/v2/group.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "nt_group_id": 4096, - "parent_group_id": 0, - "name": "example.com", - "deleted": 0 -} diff --git a/test/v2/user.json b/test/v2/user.json deleted file mode 100644 index 860b489..0000000 --- a/test/v2/user.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "nt_user_id": 4096, - "nt_group_id": 4096, - "username": "unit-test", - "email": "unit-test@example.com", - "password": "What@%Fun34Security", - "first_name": "Unit", - "last_name": "Test", - "deleted": 0 -}