adapt-authoring-config 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ name: Tests
2
+ on: push
3
+ jobs:
4
+ default:
5
+ runs-on: ubuntu-latest
6
+ permissions:
7
+ contents: read
8
+ steps:
9
+ - uses: actions/checkout@master
10
+ - uses: actions/setup-node@master
11
+ with:
12
+ node-version: 'lts/*'
13
+ cache: 'npm'
14
+ - run: npm ci
15
+ - run: npm test
package/bin/confgen.js CHANGED
@@ -10,14 +10,14 @@ import fs from 'fs/promises'
10
10
  import { globSync } from 'glob'
11
11
  import path from 'path'
12
12
  import { pathToFileURL } from 'url'
13
- import { Utils } from 'adapt-authoring-core'
13
+ import { getArgs } from 'adapt-authoring-core'
14
14
 
15
15
  const {
16
16
  defaults: useDefaults,
17
17
  params: [env],
18
18
  replace: replaceExisting,
19
19
  update: updateExisting
20
- } = Utils.getArgs()
20
+ } = getArgs()
21
21
  const NODE_ENV = env || process.env.NODE_ENV
22
22
  const confDir = path.resolve(path.join(process.cwd(), 'conf'))
23
23
  const outpath = path.join(confDir, `${NODE_ENV}.config.js`)
@@ -1,6 +1,7 @@
1
1
  /* eslint no-console: 0 */
2
2
  import { AbstractModule } from 'adapt-authoring-core'
3
3
  import chalk from 'chalk'
4
+ import { envVarToConfigKey } from './utils.js'
4
5
  import fs from 'fs/promises'
5
6
  import path from 'path'
6
7
  /**
@@ -81,23 +82,10 @@ class ConfigModule extends AbstractModule {
81
82
  try { // try to parse to allow for non-string values
82
83
  val = JSON.parse(val)
83
84
  } catch {} // ignore errors
84
- this._config[this.envVarToConfigKey(key)] = val
85
+ this._config[envVarToConfigKey(key)] = val
85
86
  })
86
87
  }
87
88
 
88
- /**
89
- * Parses an environment variable key into a format expected by this module
90
- * @param {String} envVar
91
- * @return {String} The formatted key
92
- */
93
- envVarToConfigKey (envVar) {
94
- if (envVar.startsWith('ADAPT_AUTHORING_')) {
95
- const [modPrefix, key] = envVar.split('__')
96
- return `${modPrefix.replace(/_/g, '-').toLowerCase()}.${key}`
97
- }
98
- return `env.${envVar}`
99
- }
100
-
101
89
  /**
102
90
  * Loads the relevant config file into memory
103
91
  * @return {Promise}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Parses an environment variable key into a format expected by ConfigModule
3
+ * @param {String} envVar
4
+ * @return {String} The formatted key
5
+ * @memberof config
6
+ */
7
+ export function envVarToConfigKey (envVar) {
8
+ if (envVar.startsWith('ADAPT_AUTHORING_')) {
9
+ const [modPrefix, key] = envVar.split('__')
10
+ return `${modPrefix.replace(/_/g, '-').toLowerCase()}.${key}`
11
+ }
12
+ return `env.${envVar}`
13
+ }
package/lib/utils.js ADDED
@@ -0,0 +1 @@
1
+ export { envVarToConfigKey } from './utils/envVarToConfigKey.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-config",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "A configuration module for the Adapt authoring tool.",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-config",
6
6
  "license": "GPL-3.0",
@@ -9,9 +9,12 @@
9
9
  "bin": {
10
10
  "at-confgen": "./bin/confgen.js"
11
11
  },
12
+ "scripts": {
13
+ "test": "node --test tests/*.spec.js"
14
+ },
12
15
  "repository": "github:adapt-security/adapt-authoring-config",
13
16
  "dependencies": {
14
- "adapt-authoring-core": "^1.7.0",
17
+ "adapt-authoring-core": "^2.0.0",
15
18
  "chalk": "^5.3.0",
16
19
  "glob": "^13.0.0"
17
20
  },
@@ -1,55 +1,137 @@
1
- /* global before describe, it */
1
+ import { describe, it, before } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import ConfigModule from '../lib/ConfigModule.js'
4
+ import { envVarToConfigKey } from '../lib/utils.js'
2
5
 
3
- const Config = require('../lib/configUtils')
4
- const path = require('path')
5
- // const should = require('should')
6
+ describe('ConfigModule', () => {
7
+ let instance
6
8
 
7
- describe('Config module', function () {
8
- before(function () {
9
- this.config = new Config(global.ADAPT.app, {})
10
- this.configJson = require(path.join(process.cwd(), 'conf', 'testing.config.js'))
9
+ before(async () => {
10
+ const noopHook = { tap: () => {}, untap: () => {}, invoke: async () => {} }
11
+ const noopRouter = { path: '/', createChildRouter: () => noopRouter }
12
+ const mockApp = {
13
+ config: null,
14
+ rootDir: '/test',
15
+ name: 'test-app',
16
+ dependencies: {},
17
+ dependencyloader: { moduleLoadedHook: noopHook },
18
+ waitForModule: async (...names) => {
19
+ const mocks = {
20
+ errors: { LOAD_ERROR: new Error('load') },
21
+ auth: { unsecureRoute: () => {} },
22
+ server: { api: { createChildRouter: () => noopRouter } },
23
+ jsonschema: { createSchema: async () => ({ build: async () => ({}) }) }
24
+ }
25
+ const results = names.map(n => mocks[n] || {})
26
+ return results.length === 1 ? results[0] : results
27
+ },
28
+ errors: { LOAD_ERROR: new Error('load') }
29
+ }
30
+
31
+ instance = new ConfigModule(mockApp, {})
32
+
33
+ // Wait for the async init to settle (it will error in test mode, which is fine)
34
+ try { await instance.onReady() } catch (e) { /* expected */ }
35
+
36
+ // Ensure internal state is initialized for testing
37
+ if (!instance._config) instance._config = {}
38
+ if (!instance.publicAttributes) instance.publicAttributes = []
11
39
  })
12
- describe('#initialise()', function () {
13
- it('should error on missing required attribute', runConfigInitialise('required'))
14
- it('should error on incorrect attribute type', runConfigInitialise('incorrecttype'))
15
- it('should error on validator fail', runConfigInitialise('invalid'))
40
+
41
+ describe('envVarToConfigKey()', () => {
42
+ it('should convert ADAPT_AUTHORING prefixed env vars to config keys', () => {
43
+ const result = envVarToConfigKey('ADAPT_AUTHORING_SERVER__PORT')
44
+ assert.equal(result, 'adapt-authoring-server.PORT')
45
+ })
46
+
47
+ it('should convert underscores to hyphens in module prefix', () => {
48
+ const result = envVarToConfigKey('ADAPT_AUTHORING_MY_MODULE__KEY')
49
+ assert.equal(result, 'adapt-authoring-my-module.KEY')
50
+ })
51
+
52
+ it('should prefix non-ADAPT_AUTHORING env vars with "env."', () => {
53
+ const result = envVarToConfigKey('NODE_ENV')
54
+ assert.equal(result, 'env.NODE_ENV')
55
+ })
56
+
57
+ it('should handle env vars without double underscore', () => {
58
+ // When no __ separator exists, key will be undefined
59
+ // This documents the current behavior of the function
60
+ const result = envVarToConfigKey('ADAPT_AUTHORING_TEST')
61
+ assert.equal(result, 'adapt-authoring-test.undefined')
62
+ })
16
63
  })
17
- describe('#has()', function () {
18
- it('should be able to verify a value exists', function () {
19
- const exists = this.config.has('adapt-authoring-testing.test')
20
- exists.should.be.true()
64
+
65
+ describe('#has()', () => {
66
+ it('should return true for existing config values', () => {
67
+ instance._config['test.key'] = 'value'
68
+ const exists = instance.has('test.key')
69
+ assert.equal(exists, true)
21
70
  })
22
- it('should be able to verify a value doesn\'t exist', function () {
23
- const exists = this.config.has('adapt-authoring-testing.nonono')
24
- exists.should.not.be.true()
71
+
72
+ it('should return false for non-existent config values', () => {
73
+ const exists = instance.has('nonexistent.key')
74
+ assert.equal(exists, false)
25
75
  })
26
76
  })
27
- describe('#get()', function () {
28
- it('should be able to retrieve a value', function () {
29
- const actualValue = this.config.get('adapt-authoring-testing.test')
30
- const expectedValue = this.configJson['adapt-authoring-testing'].test
31
- actualValue.should.equal(expectedValue)
77
+
78
+ describe('#get()', () => {
79
+ it('should retrieve a stored value', () => {
80
+ instance._config['test.value'] = 'expected'
81
+ const actual = instance.get('test.value')
82
+ assert.equal(actual, 'expected')
83
+ })
84
+
85
+ it('should return undefined for non-existent keys', () => {
86
+ const actual = instance.get('does.not.exist')
87
+ assert.equal(actual, undefined)
88
+ })
89
+
90
+ it('should retrieve different data types', () => {
91
+ instance._config['test.string'] = 'text'
92
+ instance._config['test.number'] = 42
93
+ instance._config['test.boolean'] = true
94
+ instance._config['test.object'] = { key: 'value' }
95
+
96
+ assert.equal(instance.get('test.string'), 'text')
97
+ assert.equal(instance.get('test.number'), 42)
98
+ assert.equal(instance.get('test.boolean'), true)
99
+ assert.deepEqual(instance.get('test.object'), { key: 'value' })
32
100
  })
33
101
  })
34
- describe('#getPublicConfig()', function () {
35
- it('should be able to retrieve values marked as public', function () {
36
- this.config.app.dependencies = [{ name: 'adapt-authoring-testing', dir: path.join(__dirname, 'data') }]
37
- this.config.initialise()
38
- const config = this.config.getPublicConfig()
39
- const value = config['adapt-authoring-testing.one']
40
- config.should.be.an.Object()
41
- value.should.equal('default')
102
+
103
+ describe('#getPublicConfig()', () => {
104
+ it('should return only public attributes', () => {
105
+ instance._config = {
106
+ 'module.public1': 'value1',
107
+ 'module.public2': 'value2',
108
+ 'module.private': 'secret'
109
+ }
110
+ instance.publicAttributes = ['module.public1', 'module.public2']
111
+
112
+ const config = instance.getPublicConfig()
113
+ assert.deepEqual(config, {
114
+ 'module.public1': 'value1',
115
+ 'module.public2': 'value2'
116
+ })
117
+ })
118
+
119
+ it('should return empty object when no public attributes exist', () => {
120
+ instance._config = { 'module.private': 'secret' }
121
+ instance.publicAttributes = []
122
+
123
+ const config = instance.getPublicConfig()
124
+ assert.deepEqual(config, {})
125
+ })
126
+
127
+ it('should handle undefined values for public attributes', () => {
128
+ instance._config = {}
129
+ instance.publicAttributes = ['module.missing']
130
+
131
+ const config = instance.getPublicConfig()
132
+ assert.deepEqual(config, {
133
+ 'module.missing': undefined
134
+ })
42
135
  })
43
136
  })
44
137
  })
45
- /**
46
- * Checks ConfigUtility#initialise
47
- * Loads the testing data in tests/data/dirname
48
- */
49
- function runConfigInitialise (dirname) {
50
- return function () {
51
- this.config.app.dependencies = [{ name: 'adapt-authoring-testing', dir: path.join(__dirname, 'data', dirname) }]
52
- this.config.initialise()
53
- this.config.errors.length.should.be.exactly(1)
54
- }
55
- }
@@ -0,0 +1,40 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { envVarToConfigKey } from '../lib/utils/envVarToConfigKey.js'
4
+
5
+ describe('envVarToConfigKey()', () => {
6
+ it('should convert ADAPT_AUTHORING_ prefixed vars to dotted config keys', () => {
7
+ assert.equal(envVarToConfigKey('ADAPT_AUTHORING_SERVER__port'), 'adapt-authoring-server.port')
8
+ })
9
+
10
+ it('should handle multi-word module names', () => {
11
+ assert.equal(envVarToConfigKey('ADAPT_AUTHORING_AUTH_LOCAL__saltRounds'), 'adapt-authoring-auth-local.saltRounds')
12
+ })
13
+
14
+ it('should convert underscores to hyphens in module prefix', () => {
15
+ assert.equal(envVarToConfigKey('ADAPT_AUTHORING_MONGODB__connectionUri'), 'adapt-authoring-mongodb.connectionUri')
16
+ })
17
+
18
+ it('should lowercase the module prefix', () => {
19
+ const result = envVarToConfigKey('ADAPT_AUTHORING_CONFIG__enableCache')
20
+ assert.ok(result.startsWith('adapt-authoring-config.'))
21
+ })
22
+
23
+ it('should prefix non-ADAPT_AUTHORING vars with "env."', () => {
24
+ assert.equal(envVarToConfigKey('NODE_ENV'), 'env.NODE_ENV')
25
+ assert.equal(envVarToConfigKey('PORT'), 'env.PORT')
26
+ assert.equal(envVarToConfigKey('HOME'), 'env.HOME')
27
+ })
28
+
29
+ it('should handle vars starting with ADAPT_ but not ADAPT_AUTHORING_', () => {
30
+ assert.equal(envVarToConfigKey('ADAPT_FOO'), 'env.ADAPT_FOO')
31
+ })
32
+
33
+ it('should handle empty string', () => {
34
+ assert.equal(envVarToConfigKey(''), 'env.')
35
+ })
36
+
37
+ it('should preserve the key part after __', () => {
38
+ assert.equal(envVarToConfigKey('ADAPT_AUTHORING_API__defaultPageSize'), 'adapt-authoring-api.defaultPageSize')
39
+ })
40
+ })
@@ -1,36 +0,0 @@
1
- /* global describe, it */
2
-
3
- const path = require('path')
4
- const utils = require('../lib/ConfigUtils')
5
- const should = require('should')
6
-
7
- describe('Config utils', function () {
8
- describe('#loadFile()', function () {
9
- it('should be able to load a valid file', function () {
10
- const filepath = path.join(__dirname, 'data', 'testfile.json')
11
- const actualContents = utils.loadFile(filepath)
12
- const expectedContents = require(filepath)
13
- actualContents.should.deepEqual(expectedContents)
14
- })
15
- it('should not error on a missing file', function () {
16
- should.doesNotThrow(function () {
17
- const contents = utils.loadFile(path.join('this', 'path', 'does', 'not', 'exist.xyz'))
18
- should(contents).be.undefined()
19
- })
20
- })
21
- })
22
- describe('#loadConfigSchema()', function () {
23
- it('should be able to load a valid schema file', function () {
24
- const dir = path.join(__dirname, 'data')
25
- const actualContents = utils.loadConfigSchema(dir)
26
- const expectedContents = require(path.join(dir, 'conf', 'config.schema.js'))
27
- actualContents.should.deepEqual(expectedContents)
28
- })
29
- it('should not error on a missing schema file', function () {
30
- should.doesNotThrow(function () {
31
- const contents = utils.loadConfigSchema(path.join(__dirname, 'doesntexist'))
32
- should(contents).be.undefined()
33
- })
34
- })
35
- })
36
- })