adapt-authoring-logger 1.1.3 → 1.3.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.
@@ -25,7 +25,7 @@ jobs:
25
25
  - name: Update npm
26
26
  run: npm install -g npm@latest
27
27
  - name: Install dependencies
28
- run: npm ci
28
+ run: npm install
29
29
  - name: Release
30
30
  env:
31
31
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -9,5 +9,5 @@ jobs:
9
9
  with:
10
10
  node-version: 'lts/*'
11
11
  cache: 'npm'
12
- - run: npm ci
12
+ - run: npm install
13
13
  - run: npx standard
@@ -1,13 +1,15 @@
1
- name: Run test scripts
1
+ name: Tests
2
2
  on: push
3
3
  jobs:
4
4
  default:
5
5
  runs-on: ubuntu-latest
6
+ permissions:
7
+ contents: read
6
8
  steps:
7
9
  - uses: actions/checkout@master
8
10
  - uses: actions/setup-node@master
9
11
  with:
10
12
  node-version: 'lts/*'
11
13
  cache: 'npm'
12
- - run: npm ci
14
+ - run: npm install
13
15
  - run: npm test
@@ -1,45 +1,12 @@
1
1
  import { AbstractModule, Hook } from 'adapt-authoring-core'
2
2
  import chalk from 'chalk'
3
+ import { colourise, getDateStamp, getModuleOverrides, isLevelEnabled, isLoggingEnabled } from './utils.js'
3
4
  /**
4
5
  * Module for logging message to the console
5
6
  * @memberof logger
6
7
  * @extends {AbstractModule}
7
8
  */
8
9
  class LoggerModule extends AbstractModule {
9
- /**
10
- * Colours an input string
11
- * @param {String} str
12
- * @param {String} colour
13
- * @return {String}
14
- */
15
- static colourise (str, colourFunc) {
16
- if (typeof colourFunc === 'string') colourFunc = chalk[colourFunc]
17
- return colourFunc ? colourFunc(str) : str
18
- }
19
-
20
- /**
21
- * Returns a formatted date stamp
22
- * @param {Object} config
23
- * @return {String}
24
- */
25
- static getDateStamp (config) {
26
- if (!config.timestamp) {
27
- return ''
28
- }
29
- let str
30
- if (config.dateFormat === 'iso') {
31
- str = new Date().toISOString()
32
- } else if (config.dateFormat === 'short') {
33
- const d = new Date()
34
- const m = d.getMonth() + 1
35
- const s = d.getSeconds()
36
- const date = `${d.getDate()}/${m < 10 ? `0${m}` : m}/${d.getFullYear().toString().slice(2)}`
37
- const time = `${d.getHours()}:${d.getMinutes()}:${s < 10 ? `0${s}` : s}`
38
- str = `${date}-${time}`
39
- }
40
- return LoggerModule.colourise(`${str} `, chalk.dim)
41
- }
42
-
43
10
  /** @override */
44
11
  async init () {
45
12
  await this.app.waitForModule('config')
@@ -60,33 +27,33 @@ class LoggerModule extends AbstractModule {
60
27
  this.config = {
61
28
  levels: {
62
29
  error: {
63
- enable: this.isLevelEnabled('error'),
64
- moduleOverrides: this.getModuleOverrides('error'),
30
+ enable: isLevelEnabled(this.levelsConfig, 'error'),
31
+ moduleOverrides: getModuleOverrides(this.levelsConfig, 'error'),
65
32
  colour: chalk.red
66
33
  },
67
34
  warn: {
68
- enable: this.isLevelEnabled('warn'),
69
- moduleOverrides: this.getModuleOverrides('warn'),
35
+ enable: isLevelEnabled(this.levelsConfig, 'warn'),
36
+ moduleOverrides: getModuleOverrides(this.levelsConfig, 'warn'),
70
37
  colour: chalk.yellow
71
38
  },
72
39
  success: {
73
- enable: this.isLevelEnabled('success'),
74
- moduleOverrides: this.getModuleOverrides('success'),
40
+ enable: isLevelEnabled(this.levelsConfig, 'success'),
41
+ moduleOverrides: getModuleOverrides(this.levelsConfig, 'success'),
75
42
  colour: chalk.green
76
43
  },
77
44
  info: {
78
- enable: this.isLevelEnabled('info'),
79
- moduleOverrides: this.getModuleOverrides('info'),
45
+ enable: isLevelEnabled(this.levelsConfig, 'info'),
46
+ moduleOverrides: getModuleOverrides(this.levelsConfig, 'info'),
80
47
  colour: chalk.cyan
81
48
  },
82
49
  debug: {
83
- enable: this.isLevelEnabled('debug'),
84
- moduleOverrides: this.getModuleOverrides('debug'),
50
+ enable: isLevelEnabled(this.levelsConfig, 'debug'),
51
+ moduleOverrides: getModuleOverrides(this.levelsConfig, 'debug'),
85
52
  colour: chalk.dim
86
53
  },
87
54
  verbose: {
88
- enable: this.isLevelEnabled('verbose'),
89
- moduleOverrides: this.getModuleOverrides('verbose'),
55
+ enable: isLevelEnabled(this.levelsConfig, 'verbose'),
56
+ moduleOverrides: getModuleOverrides(this.levelsConfig, 'verbose'),
90
57
  colour: chalk.grey.italic
91
58
  }
92
59
  },
@@ -100,42 +67,6 @@ class LoggerModule extends AbstractModule {
100
67
  this.app.logger = this
101
68
  }
102
69
 
103
- /**
104
- * Determines whether a specific log level is enabled
105
- * @param {String} level
106
- * @return {Boolean}
107
- */
108
- isLevelEnabled (level) { // note explicit disable takes preference
109
- return !this.levelsConfig.includes(`!${level}`) && this.levelsConfig.includes(level)
110
- }
111
-
112
- /**
113
- * Returns a list of log levels with overrides, either inclusive or exclusive
114
- * @param {String} level
115
- * @return {Array}
116
- */
117
- getModuleOverrides (level) {
118
- const levels = []
119
- this.levelsConfig.forEach(l => {
120
- const s = `${level}.`; const notS = `!${level}.`
121
- if (l.indexOf(s) === 0 || l.indexOf(notS) === 0) levels.push(l)
122
- })
123
- return levels
124
- }
125
-
126
- /**
127
- * Returns whether a message should be logged (i.e. not disabled in the config)
128
- * @param {string} level Logging level
129
- * @param {string} id Id of log caller
130
- * @returns {boolean}
131
- */
132
- isLoggingEnabled (level, id) {
133
- const { enable, moduleOverrides = [] } = this?.config?.levels[level] || {}
134
- const isEnabled = enable || moduleOverrides.includes(`${level}.${id}`)
135
- const disableOverride = moduleOverrides.includes(`!${level}.${id}`)
136
- return isEnabled && !disableOverride
137
- }
138
-
139
70
  /**
140
71
  * Logs a message to the console
141
72
  * @param {String} level Severity of the message
@@ -150,12 +81,12 @@ class LoggerModule extends AbstractModule {
150
81
  id = this.name.split('-').pop()
151
82
  args = [AbstractModule.MODULE_READY, ...args]
152
83
  }
153
- if (!this.isLoggingEnabled(level, id)) {
84
+ if (!isLoggingEnabled(this.config?.levels, level, id)) {
154
85
  return
155
86
  }
156
87
  const colour = this?.config?.levels[level]?.colour
157
88
  const logFunc = console[level] ?? console.log
158
- logFunc(`${LoggerModule.getDateStamp(this.config)}${LoggerModule.colourise(level, colour)} ${LoggerModule.colourise(id, chalk.magenta)}`, ...args)
89
+ logFunc(`${getDateStamp(this.config)}${colourise(level, colour)} ${colourise(id, chalk.magenta)}`, ...args)
159
90
  this.logHook.invoke(new Date(), level, id, ...args)
160
91
  }
161
92
  }
@@ -0,0 +1,12 @@
1
+ import chalk from 'chalk'
2
+ /**
3
+ * Colours an input string using a chalk function or colour name
4
+ * @param {String} str The string to colourise
5
+ * @param {String|Function} colourFunc A chalk colour function or string name of a chalk colour
6
+ * @return {String} The colourised string
7
+ * @memberof logger
8
+ */
9
+ export function colourise (str, colourFunc) {
10
+ if (typeof colourFunc === 'string') colourFunc = chalk[colourFunc]
11
+ return colourFunc ? colourFunc(str) : str
12
+ }
@@ -0,0 +1,27 @@
1
+ import chalk from 'chalk'
2
+ import { colourise } from './colourise.js'
3
+ /**
4
+ * Returns a formatted date stamp string based on config
5
+ * @param {Object} config Logger configuration object
6
+ * @param {Boolean} config.timestamp Whether to include a timestamp
7
+ * @param {String} config.dateFormat Date format ('iso' or 'short')
8
+ * @return {String} The formatted date stamp (empty string if timestamps disabled)
9
+ * @memberof logger
10
+ */
11
+ export function getDateStamp (config) {
12
+ if (!config.timestamp) {
13
+ return ''
14
+ }
15
+ let str
16
+ if (config.dateFormat === 'iso') {
17
+ str = new Date().toISOString()
18
+ } else if (config.dateFormat === 'short') {
19
+ const d = new Date()
20
+ const m = d.getMonth() + 1
21
+ const s = d.getSeconds()
22
+ const date = `${d.getDate()}/${m < 10 ? `0${m}` : m}/${d.getFullYear().toString().slice(2)}`
23
+ const time = `${d.getHours()}:${d.getMinutes()}:${s < 10 ? `0${s}` : s}`
24
+ str = `${date}-${time}`
25
+ }
26
+ return colourise(`${str} `, chalk.dim)
27
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Returns a list of module-specific log level overrides from the levels config
3
+ * @param {Array<String>} levelsConfig Array of level configuration strings
4
+ * @param {String} level The log level to find overrides for
5
+ * @return {Array<String>} Array of override strings (e.g. ['debug.mymod', '!debug.other'])
6
+ * @memberof logger
7
+ */
8
+ export function getModuleOverrides (levelsConfig, level) {
9
+ const levels = []
10
+ levelsConfig.forEach(l => {
11
+ const s = `${level}.`; const notS = `!${level}.`
12
+ if (l.indexOf(s) === 0 || l.indexOf(notS) === 0) levels.push(l)
13
+ })
14
+ return levels
15
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Determines whether a specific log level is enabled in the levels config
3
+ * @param {Array<String>} levelsConfig Array of level configuration strings
4
+ * @param {String} level The log level to check (e.g. 'error', 'warn', 'debug')
5
+ * @return {Boolean} Whether the level is enabled
6
+ * @memberof logger
7
+ */
8
+ export function isLevelEnabled (levelsConfig, level) {
9
+ return !levelsConfig.includes(`!${level}`) && levelsConfig.includes(level)
10
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Returns whether a message should be logged based on the resolved config
3
+ * @param {Object} configLevels The resolved levels config object (e.g. { error: { enable, moduleOverrides }, ... })
4
+ * @param {String} level Logging level (e.g. 'error', 'warn', 'debug')
5
+ * @param {String} id Id of log caller (module name)
6
+ * @returns {Boolean} Whether logging is enabled for this level and caller
7
+ * @memberof logger
8
+ */
9
+ export function isLoggingEnabled (configLevels, level, id) {
10
+ const { enable, moduleOverrides = [] } = configLevels?.[level] || {}
11
+ const isEnabled = enable || moduleOverrides.includes(`${level}.${id}`)
12
+ const disableOverride = moduleOverrides.includes(`!${level}.${id}`)
13
+ return isEnabled && !disableOverride
14
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,5 @@
1
+ export { colourise } from './utils/colourise.js'
2
+ export { getDateStamp } from './utils/getDateStamp.js'
3
+ export { getModuleOverrides } from './utils/getModuleOverrides.js'
4
+ export { isLevelEnabled } from './utils/isLevelEnabled.js'
5
+ export { isLoggingEnabled } from './utils/isLoggingEnabled.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-logger",
3
- "version": "1.1.3",
3
+ "version": "1.3.0",
4
4
  "description": "Basic logger for the Adapt authoring tool",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-logger",
6
6
  "license": "GPL-3.0",
@@ -8,18 +8,15 @@
8
8
  "main": "index.js",
9
9
  "repository": "adapt-security/adapt-authoring-logger",
10
10
  "dependencies": {
11
+ "adapt-authoring-core": "^2.0.0",
11
12
  "chalk": "^5.3.0"
12
13
  },
13
14
  "peerDependencies": {
14
- "adapt-authoring-config": "^1.1.4",
15
- "adapt-authoring-core": "^1.7.0"
15
+ "adapt-authoring-config": "^1.1.4"
16
16
  },
17
17
  "peerDependenciesMeta": {
18
18
  "adapt-authoring-config": {
19
19
  "optional": true
20
- },
21
- "adapt-authoring-core": {
22
- "optional": true
23
20
  }
24
21
  },
25
22
  "devDependencies": {
@@ -56,6 +53,6 @@
56
53
  ]
57
54
  },
58
55
  "scripts": {
59
- "test": "node --test"
56
+ "test": "node --test tests/*.spec.js"
60
57
  }
61
58
  }
@@ -0,0 +1,334 @@
1
+ import { describe, it, beforeEach, afterEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import chalk from 'chalk'
4
+ import { AbstractModule } from 'adapt-authoring-core'
5
+ import LoggerModule from '../lib/LoggerModule.js'
6
+ import { colourise, getDateStamp, isLevelEnabled, getModuleOverrides, isLoggingEnabled } from '../lib/utils.js'
7
+
8
+ describe('LoggerModule', () => {
9
+ describe('colourise()', () => {
10
+ it('should return string with colour function applied', () => {
11
+ const result = colourise('test', chalk.red)
12
+ assert.ok(result.includes('test'))
13
+ })
14
+
15
+ it('should accept colour name as string', () => {
16
+ const result = colourise('test', 'green')
17
+ assert.ok(result.includes('test'))
18
+ })
19
+
20
+ it('should return uncoloured string if no colour function provided', () => {
21
+ const result = colourise('test', null)
22
+ assert.equal(result, 'test')
23
+ })
24
+
25
+ it('should handle undefined colour function', () => {
26
+ const result = colourise('test', undefined)
27
+ assert.equal(result, 'test')
28
+ })
29
+
30
+ it('should return empty string unchanged when no colour', () => {
31
+ const result = colourise('', null)
32
+ assert.equal(result, '')
33
+ })
34
+
35
+ it('should apply colour function to empty string', () => {
36
+ const result = colourise('', chalk.red)
37
+ assert.equal(typeof result, 'string')
38
+ })
39
+ })
40
+
41
+ describe('getDateStamp()', () => {
42
+ it('should return empty string when timestamp is disabled', () => {
43
+ const config = { timestamp: false }
44
+ const result = getDateStamp(config)
45
+ assert.equal(result, '')
46
+ })
47
+
48
+ it('should return ISO format date when dateFormat is "iso"', () => {
49
+ const config = { timestamp: true, dateFormat: 'iso' }
50
+ const result = getDateStamp(config)
51
+ assert.ok(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(result))
52
+ })
53
+
54
+ it('should return short format date when dateFormat is "short"', () => {
55
+ const config = { timestamp: true, dateFormat: 'short' }
56
+ const result = getDateStamp(config)
57
+ assert.ok(/\d{1,2}\/\d{2}\/\d{2}-\d{1,2}:\d{1,2}:\d{2}/.test(result))
58
+ })
59
+
60
+ it('should return undefined-based string for unrecognised dateFormat', () => {
61
+ const config = { timestamp: true, dateFormat: 'unknown' }
62
+ const result = getDateStamp(config)
63
+ assert.ok(result.includes('undefined'))
64
+ })
65
+
66
+ it('should include trailing space in formatted timestamp', () => {
67
+ const config = { timestamp: true, dateFormat: 'iso' }
68
+ const result = getDateStamp(config)
69
+ assert.ok(result.includes(' '))
70
+ })
71
+ })
72
+
73
+ describe('isLevelEnabled()', () => {
74
+ it('should return true for enabled levels', () => {
75
+ const levelsConfig = ['error', 'warn', 'info']
76
+ assert.equal(isLevelEnabled(levelsConfig, 'error'), true)
77
+ assert.equal(isLevelEnabled(levelsConfig, 'warn'), true)
78
+ assert.equal(isLevelEnabled(levelsConfig, 'info'), true)
79
+ })
80
+
81
+ it('should return false for disabled levels', () => {
82
+ const levelsConfig = ['error', 'warn', 'info']
83
+ assert.equal(isLevelEnabled(levelsConfig, 'debug'), false)
84
+ assert.equal(isLevelEnabled(levelsConfig, 'verbose'), false)
85
+ })
86
+
87
+ it('should return false when level is explicitly disabled', () => {
88
+ const levelsConfig = ['error', '!warn', 'info']
89
+ assert.equal(isLevelEnabled(levelsConfig, 'warn'), false)
90
+ })
91
+
92
+ it('should give preference to explicit disable', () => {
93
+ const levelsConfig = ['warn', '!warn']
94
+ assert.equal(isLevelEnabled(levelsConfig, 'warn'), false)
95
+ })
96
+
97
+ it('should return false for empty levelsConfig', () => {
98
+ assert.equal(isLevelEnabled([], 'error'), false)
99
+ })
100
+
101
+ it('should not match partial level names', () => {
102
+ const levelsConfig = ['info']
103
+ assert.equal(isLevelEnabled(levelsConfig, 'inf'), false)
104
+ assert.equal(isLevelEnabled(levelsConfig, 'information'), false)
105
+ })
106
+ })
107
+
108
+ describe('getModuleOverrides()', () => {
109
+ it('should return module-specific overrides for a level', () => {
110
+ const levelsConfig = ['error', 'error.myModule', 'error.anotherModule', 'warn']
111
+ const result = getModuleOverrides(levelsConfig, 'error')
112
+ assert.ok(result.includes('error.myModule'))
113
+ assert.ok(result.includes('error.anotherModule'))
114
+ assert.equal(result.length, 2)
115
+ })
116
+
117
+ it('should include negative overrides', () => {
118
+ const levelsConfig = ['error', '!error.myModule']
119
+ const result = getModuleOverrides(levelsConfig, 'error')
120
+ assert.ok(result.includes('!error.myModule'))
121
+ })
122
+
123
+ it('should return empty array when no overrides exist', () => {
124
+ const levelsConfig = ['error', 'warn']
125
+ const result = getModuleOverrides(levelsConfig, 'info')
126
+ assert.equal(result.length, 0)
127
+ })
128
+
129
+ it('should not include overrides for other levels', () => {
130
+ const levelsConfig = ['error', 'error.moduleA', 'warn.moduleB']
131
+ const result = getModuleOverrides(levelsConfig, 'error')
132
+ assert.ok(!result.includes('warn.moduleB'))
133
+ })
134
+
135
+ it('should return both positive and negative overrides together', () => {
136
+ const levelsConfig = ['error', 'error.modA', '!error.modB']
137
+ const result = getModuleOverrides(levelsConfig, 'error')
138
+ assert.equal(result.length, 2)
139
+ assert.ok(result.includes('error.modA'))
140
+ assert.ok(result.includes('!error.modB'))
141
+ })
142
+
143
+ it('should return empty array for empty levelsConfig', () => {
144
+ assert.deepEqual(getModuleOverrides([], 'error'), [])
145
+ })
146
+ })
147
+
148
+ describe('isLoggingEnabled()', () => {
149
+ it('should return true for enabled levels', () => {
150
+ const levels = { error: { enable: true, moduleOverrides: [] } }
151
+ assert.equal(isLoggingEnabled(levels, 'error', 'anyId'), true)
152
+ })
153
+
154
+ it('should return false for disabled levels without overrides', () => {
155
+ const levels = { warn: { enable: false, moduleOverrides: ['warn.specific'] } }
156
+ assert.equal(isLoggingEnabled(levels, 'warn', 'generic'), false)
157
+ })
158
+
159
+ it('should return true for disabled level with positive override', () => {
160
+ const levels = { warn: { enable: false, moduleOverrides: ['warn.specific'] } }
161
+ assert.equal(isLoggingEnabled(levels, 'warn', 'specific'), true)
162
+ })
163
+
164
+ it('should return false for enabled level with negative override', () => {
165
+ const levels = { info: { enable: true, moduleOverrides: ['!info.blocked'] } }
166
+ assert.equal(isLoggingEnabled(levels, 'info', 'blocked'), false)
167
+ })
168
+
169
+ it('should return true for enabled level without override', () => {
170
+ const levels = { info: { enable: true, moduleOverrides: ['!info.blocked'] } }
171
+ assert.equal(isLoggingEnabled(levels, 'info', 'allowed'), true)
172
+ })
173
+
174
+ it('should handle missing level config gracefully', () => {
175
+ const levels = { error: { enable: true, moduleOverrides: [] } }
176
+ assert.equal(isLoggingEnabled(levels, 'nonexistent', 'id'), false)
177
+ })
178
+
179
+ it('should default moduleOverrides to empty array when undefined', () => {
180
+ const levels = { error: { enable: true } }
181
+ assert.equal(isLoggingEnabled(levels, 'error', 'anyId'), true)
182
+ })
183
+
184
+ it('should return false when both enable is false and no matching override', () => {
185
+ const levels = { debug: { enable: false, moduleOverrides: ['debug.other'] } }
186
+ assert.equal(isLoggingEnabled(levels, 'debug', 'notOther'), false)
187
+ })
188
+
189
+ it('should handle config being undefined gracefully', () => {
190
+ assert.equal(isLoggingEnabled(undefined, 'error', 'id'), false)
191
+ })
192
+ })
193
+
194
+ describe('#log()', () => {
195
+ let logger
196
+ let logOutput
197
+ let originalConsoleLog
198
+
199
+ beforeEach(() => {
200
+ logger = new LoggerModule('test-logger')
201
+ logger.config = {
202
+ levels: {
203
+ error: { enable: true, moduleOverrides: [], colour: chalk.red },
204
+ warn: { enable: true, moduleOverrides: [], colour: chalk.yellow },
205
+ info: { enable: true, moduleOverrides: [], colour: chalk.cyan },
206
+ debug: { enable: true, moduleOverrides: [], colour: chalk.dim }
207
+ },
208
+ timestamp: false,
209
+ dateFormat: 'iso',
210
+ mute: false
211
+ }
212
+ logger.logHook = { invoke: () => {} }
213
+ logOutput = []
214
+
215
+ originalConsoleLog = console.log
216
+ console.log = (...args) => logOutput.push({ level: 'log', args })
217
+ console.error = (...args) => logOutput.push({ level: 'error', args })
218
+ console.warn = (...args) => logOutput.push({ level: 'warn', args })
219
+ console.info = (...args) => logOutput.push({ level: 'info', args })
220
+ })
221
+
222
+ afterEach(() => {
223
+ console.log = originalConsoleLog
224
+ })
225
+
226
+ it('should not log when muted', () => {
227
+ logger.config.mute = true
228
+ logger.log('info', 'test', 'message')
229
+ assert.equal(logOutput.length, 0)
230
+ })
231
+
232
+ it('should not log when level is disabled', () => {
233
+ logger.config.levels.debug.enable = false
234
+ logger.log('debug', 'test', 'message')
235
+ assert.equal(logOutput.length, 0)
236
+ })
237
+
238
+ it('should log message when enabled', () => {
239
+ logger.log('info', 'testId', 'test message')
240
+ assert.equal(logOutput.length, 1)
241
+ assert.ok(logOutput[0].args.some(arg => typeof arg === 'string' && arg.includes('testId')))
242
+ })
243
+
244
+ it('should include multiple arguments', () => {
245
+ logger.log('info', 'test', 'arg1', 'arg2', 'arg3')
246
+ assert.equal(logOutput.length, 1)
247
+ const args = logOutput[0].args
248
+ assert.ok(args.includes('arg1'))
249
+ assert.ok(args.includes('arg2'))
250
+ assert.ok(args.includes('arg3'))
251
+ })
252
+
253
+ it('should use correct console method for level', () => {
254
+ logger.log('error', 'test', 'message')
255
+ assert.equal(logOutput[0].level, 'error')
256
+ })
257
+
258
+ it('should invoke logHook with correct parameters', () => {
259
+ let hookArgs = null
260
+ logger.logHook.invoke = (...args) => {
261
+ hookArgs = args
262
+ }
263
+ logger.log('info', 'testId', 'message')
264
+ assert.ok(hookArgs)
265
+ assert.equal(hookArgs[1], 'info')
266
+ assert.equal(hookArgs[2], 'testId')
267
+ assert.equal(hookArgs[3], 'message')
268
+ })
269
+
270
+ it('should colorise level in output', () => {
271
+ logger.log('info', 'test', 'message')
272
+ assert.equal(logOutput.length, 1)
273
+ assert.ok(logOutput[0].args[0].includes('info'))
274
+ })
275
+
276
+ it('should colorise id in output', () => {
277
+ logger.log('info', 'myModule', 'message')
278
+ assert.equal(logOutput.length, 1)
279
+ assert.ok(logOutput[0].args[0].includes('myModule'))
280
+ })
281
+
282
+ it('should treat string "true" mute value as muted', () => {
283
+ logger.config.mute = 'true'
284
+ logger.log('info', 'test', 'message')
285
+ assert.equal(logOutput.length, 0)
286
+ })
287
+
288
+ it('should not mute when mute is "false"', () => {
289
+ logger.config.mute = 'false'
290
+ logger.log('info', 'test', 'message')
291
+ assert.equal(logOutput.length, 1)
292
+ })
293
+
294
+ it('should fall back to console.log for unknown level', () => {
295
+ logger.config.levels.success = { enable: true, moduleOverrides: [], colour: chalk.green }
296
+ logger.log('success', 'test', 'message')
297
+ assert.equal(logOutput.length, 1)
298
+ assert.equal(logOutput[0].level, 'log')
299
+ })
300
+
301
+ it('should handle MODULE_READY id by using module name suffix', () => {
302
+ logger.name = 'adapt-authoring-logger'
303
+ logger.config.levels.verbose = { enable: true, moduleOverrides: [], colour: chalk.grey }
304
+ let hookArgs = null
305
+ logger.logHook.invoke = (...args) => { hookArgs = args }
306
+ logger.log('verbose', AbstractModule.MODULE_READY, 'some-init-time')
307
+ assert.equal(logOutput.length, 1)
308
+ assert.ok(logOutput[0].args[0].includes('logger'))
309
+ assert.equal(hookArgs[2], 'logger')
310
+ assert.equal(hookArgs[3], AbstractModule.MODULE_READY)
311
+ assert.equal(hookArgs[4], 'some-init-time')
312
+ })
313
+
314
+ it('should prepend date stamp when timestamp is enabled', () => {
315
+ logger.config.timestamp = true
316
+ logger.config.dateFormat = 'iso'
317
+ logger.log('info', 'test', 'message')
318
+ assert.equal(logOutput.length, 1)
319
+ assert.ok(/\d{4}-\d{2}-\d{2}T/.test(logOutput[0].args[0]))
320
+ })
321
+
322
+ it('should pass Date as first argument to logHook', () => {
323
+ let hookArgs = null
324
+ logger.logHook.invoke = (...args) => { hookArgs = args }
325
+ logger.log('info', 'test', 'message')
326
+ assert.ok(hookArgs[0] instanceof Date)
327
+ })
328
+
329
+ it('should handle config being undefined gracefully', () => {
330
+ logger.config = undefined
331
+ assert.doesNotThrow(() => logger.log('info', 'test', 'message'))
332
+ })
333
+ })
334
+ })
@@ -0,0 +1,36 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { colourise } from '../lib/utils/colourise.js'
4
+
5
+ describe('colourise()', () => {
6
+ it('should return string unchanged when colourFunc is undefined', () => {
7
+ assert.equal(colourise('hello', undefined), 'hello')
8
+ })
9
+
10
+ it('should return string unchanged when colourFunc is null', () => {
11
+ assert.equal(colourise('hello', null), 'hello')
12
+ })
13
+
14
+ it('should apply a function colour', () => {
15
+ const mockColour = (s) => `[coloured]${s}[/coloured]`
16
+ assert.equal(colourise('hello', mockColour), '[coloured]hello[/coloured]')
17
+ })
18
+
19
+ it('should resolve string colour names via chalk', () => {
20
+ // chalk resolves 'red' to chalk.red and applies it
21
+ const result = colourise('hello', 'red')
22
+ assert.ok(result.includes('hello'))
23
+ // chalk.red is a function, so the result should be the return value of that function
24
+ // In non-TTY environments chalk may strip ANSI codes, so just verify it resolved and ran
25
+ assert.equal(typeof result, 'string')
26
+ })
27
+
28
+ it('should return string unchanged for invalid chalk colour name', () => {
29
+ assert.equal(colourise('hello', 'notARealColour'), 'hello')
30
+ })
31
+
32
+ it('should handle empty string input', () => {
33
+ const mockColour = (s) => `[c]${s}[/c]`
34
+ assert.equal(colourise('', mockColour), '[c][/c]')
35
+ })
36
+ })
@@ -0,0 +1,34 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { getDateStamp } from '../lib/utils/getDateStamp.js'
4
+
5
+ // eslint-disable-next-line no-control-regex
6
+ const ANSI_REGEX = /\u001b\[\d+m/g
7
+
8
+ describe('getDateStamp()', () => {
9
+ it('should return empty string when timestamp is falsy', () => {
10
+ assert.equal(getDateStamp({ timestamp: false, dateFormat: 'iso' }), '')
11
+ })
12
+
13
+ it('should return empty string when timestamp is undefined', () => {
14
+ assert.equal(getDateStamp({ dateFormat: 'iso' }), '')
15
+ })
16
+
17
+ it('should return an ISO date string when dateFormat is "iso"', () => {
18
+ const result = getDateStamp({ timestamp: true, dateFormat: 'iso' })
19
+ const stripped = result.replace(ANSI_REGEX, '')
20
+ assert.ok(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z /.test(stripped))
21
+ })
22
+
23
+ it('should return a short date string when dateFormat is "short"', () => {
24
+ const result = getDateStamp({ timestamp: true, dateFormat: 'short' })
25
+ const stripped = result.replace(ANSI_REGEX, '')
26
+ assert.ok(/^\d{1,2}\/\d{2}\/\d{2}-\d{1,2}:\d{1,2}:\d{1,2} /.test(stripped))
27
+ })
28
+
29
+ it('should handle unknown dateFormat by returning "undefined " (colourised)', () => {
30
+ const result = getDateStamp({ timestamp: true, dateFormat: 'unknown' })
31
+ const stripped = result.replace(ANSI_REGEX, '')
32
+ assert.equal(stripped, 'undefined ')
33
+ })
34
+ })
@@ -0,0 +1,39 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { getModuleOverrides } from '../lib/utils/getModuleOverrides.js'
4
+
5
+ describe('getModuleOverrides()', () => {
6
+ it('should return empty array when no overrides exist', () => {
7
+ assert.deepEqual(getModuleOverrides(['error', 'warn', 'info'], 'debug'), [])
8
+ })
9
+
10
+ it('should return module-specific includes', () => {
11
+ const config = ['error', 'debug.mymod', 'debug.other']
12
+ assert.deepEqual(getModuleOverrides(config, 'debug'), ['debug.mymod', 'debug.other'])
13
+ })
14
+
15
+ it('should return module-specific excludes', () => {
16
+ const config = ['error', '!debug.mymod']
17
+ assert.deepEqual(getModuleOverrides(config, 'debug'), ['!debug.mymod'])
18
+ })
19
+
20
+ it('should return both includes and excludes', () => {
21
+ const config = ['error', 'debug.mymod', '!debug.other', 'debug.third']
22
+ const result = getModuleOverrides(config, 'debug')
23
+ assert.deepEqual(result, ['debug.mymod', '!debug.other', 'debug.third'])
24
+ })
25
+
26
+ it('should not include plain level entries', () => {
27
+ const config = ['debug', 'debug.mymod']
28
+ assert.deepEqual(getModuleOverrides(config, 'debug'), ['debug.mymod'])
29
+ })
30
+
31
+ it('should not match other levels with similar prefixes', () => {
32
+ const config = ['error.mymod', 'warn.mymod']
33
+ assert.deepEqual(getModuleOverrides(config, 'error'), ['error.mymod'])
34
+ })
35
+
36
+ it('should handle empty config', () => {
37
+ assert.deepEqual(getModuleOverrides([], 'error'), [])
38
+ })
39
+ })
@@ -0,0 +1,33 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { isLevelEnabled } from '../lib/utils/isLevelEnabled.js'
4
+
5
+ describe('isLevelEnabled()', () => {
6
+ it('should return true when level is in config', () => {
7
+ assert.equal(isLevelEnabled(['error', 'warn', 'info'], 'error'), true)
8
+ })
9
+
10
+ it('should return false when level is not in config', () => {
11
+ assert.equal(isLevelEnabled(['error', 'warn'], 'debug'), false)
12
+ })
13
+
14
+ it('should return false when level is explicitly disabled', () => {
15
+ assert.equal(isLevelEnabled(['error', '!warn', 'warn'], 'warn'), false)
16
+ })
17
+
18
+ it('should return true when level is present and not explicitly disabled', () => {
19
+ assert.equal(isLevelEnabled(['error', 'warn', '!debug'], 'warn'), true)
20
+ })
21
+
22
+ it('should return false for empty config', () => {
23
+ assert.equal(isLevelEnabled([], 'error'), false)
24
+ })
25
+
26
+ it('should not match partial level names', () => {
27
+ assert.equal(isLevelEnabled(['error.mymod'], 'error'), false)
28
+ })
29
+
30
+ it('should handle explicit disable taking precedence even when level is included', () => {
31
+ assert.equal(isLevelEnabled(['debug', '!debug'], 'debug'), false)
32
+ })
33
+ })
@@ -0,0 +1,50 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { isLoggingEnabled } from '../lib/utils/isLoggingEnabled.js'
4
+
5
+ describe('isLoggingEnabled()', () => {
6
+ it('should return true when level is globally enabled', () => {
7
+ const levels = { error: { enable: true, moduleOverrides: [] } }
8
+ assert.equal(isLoggingEnabled(levels, 'error', 'mymod'), true)
9
+ })
10
+
11
+ it('should return false when level is globally disabled and no override', () => {
12
+ const levels = { error: { enable: false, moduleOverrides: [] } }
13
+ assert.equal(isLoggingEnabled(levels, 'error', 'mymod'), false)
14
+ })
15
+
16
+ it('should return true when module has an include override', () => {
17
+ const levels = { debug: { enable: false, moduleOverrides: ['debug.mymod'] } }
18
+ assert.equal(isLoggingEnabled(levels, 'debug', 'mymod'), true)
19
+ })
20
+
21
+ it('should return false when module has a disable override', () => {
22
+ const levels = { error: { enable: true, moduleOverrides: ['!error.mymod'] } }
23
+ assert.equal(isLoggingEnabled(levels, 'error', 'mymod'), false)
24
+ })
25
+
26
+ it('should return false when both include and disable override exist', () => {
27
+ const levels = { debug: { enable: false, moduleOverrides: ['debug.mymod', '!debug.mymod'] } }
28
+ assert.equal(isLoggingEnabled(levels, 'debug', 'mymod'), false)
29
+ })
30
+
31
+ it('should return false when level is not in config', () => {
32
+ const levels = { error: { enable: true, moduleOverrides: [] } }
33
+ assert.equal(isLoggingEnabled(levels, 'debug', 'mymod'), false)
34
+ })
35
+
36
+ it('should handle null/undefined configLevels gracefully', () => {
37
+ assert.equal(isLoggingEnabled(null, 'error', 'mymod'), false)
38
+ assert.equal(isLoggingEnabled(undefined, 'error', 'mymod'), false)
39
+ })
40
+
41
+ it('should default moduleOverrides to empty array when missing', () => {
42
+ const levels = { error: { enable: true } }
43
+ assert.equal(isLoggingEnabled(levels, 'error', 'mymod'), true)
44
+ })
45
+
46
+ it('should not match overrides for different modules', () => {
47
+ const levels = { debug: { enable: false, moduleOverrides: ['debug.other'] } }
48
+ assert.equal(isLoggingEnabled(levels, 'debug', 'mymod'), false)
49
+ })
50
+ })
@@ -1,240 +0,0 @@
1
- import assert from 'assert'
2
- import { describe, it, beforeEach, afterEach } from 'node:test'
3
- import chalk from 'chalk'
4
- import LoggerModule from '../lib/LoggerModule.js'
5
-
6
- describe('LoggerModule', () => {
7
- describe('colourise()', () => {
8
- it('should return string with colour function applied', () => {
9
- const result = LoggerModule.colourise('test', chalk.red)
10
- assert.ok(result.includes('test'))
11
- })
12
-
13
- it('should accept colour name as string', () => {
14
- const result = LoggerModule.colourise('test', 'green')
15
- assert.ok(result.includes('test'))
16
- })
17
-
18
- it('should return uncoloured string if no colour function provided', () => {
19
- const result = LoggerModule.colourise('test', null)
20
- assert.strictEqual(result, 'test')
21
- })
22
-
23
- it('should handle undefined colour function', () => {
24
- const result = LoggerModule.colourise('test', undefined)
25
- assert.strictEqual(result, 'test')
26
- })
27
- })
28
-
29
- describe('getDateStamp()', () => {
30
- it('should return empty string when timestamp is disabled', () => {
31
- const config = { timestamp: false }
32
- const result = LoggerModule.getDateStamp(config)
33
- assert.strictEqual(result, '')
34
- })
35
-
36
- it('should return ISO format date when dateFormat is "iso"', () => {
37
- const config = { timestamp: true, dateFormat: 'iso' }
38
- const result = LoggerModule.getDateStamp(config)
39
- assert.ok(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(result))
40
- })
41
-
42
- it('should return short format date when dateFormat is "short"', () => {
43
- const config = { timestamp: true, dateFormat: 'short' }
44
- const result = LoggerModule.getDateStamp(config)
45
- assert.ok(/\d{1,2}\/\d{2}\/\d{2}-\d{2}:\d{2}:\d{2}/.test(result))
46
- })
47
- })
48
-
49
- describe('isLevelEnabled()', () => {
50
- let logger
51
-
52
- beforeEach(() => {
53
- logger = new LoggerModule('test-logger')
54
- logger.levelsConfig = ['error', 'warn', 'info']
55
- })
56
-
57
- it('should return true for enabled levels', () => {
58
- assert.strictEqual(logger.isLevelEnabled('error'), true)
59
- assert.strictEqual(logger.isLevelEnabled('warn'), true)
60
- assert.strictEqual(logger.isLevelEnabled('info'), true)
61
- })
62
-
63
- it('should return false for disabled levels', () => {
64
- assert.strictEqual(logger.isLevelEnabled('debug'), false)
65
- assert.strictEqual(logger.isLevelEnabled('verbose'), false)
66
- })
67
-
68
- it('should return false when level is explicitly disabled', () => {
69
- logger.levelsConfig = ['error', '!warn', 'info']
70
- assert.strictEqual(logger.isLevelEnabled('warn'), false)
71
- })
72
-
73
- it('should give preference to explicit disable', () => {
74
- logger.levelsConfig = ['warn', '!warn']
75
- assert.strictEqual(logger.isLevelEnabled('warn'), false)
76
- })
77
- })
78
-
79
- describe('getModuleOverrides()', () => {
80
- let logger
81
-
82
- beforeEach(() => {
83
- logger = new LoggerModule('test-logger')
84
- })
85
-
86
- it('should return module-specific overrides for a level', () => {
87
- logger.levelsConfig = ['error', 'error.myModule', 'error.anotherModule', 'warn']
88
- const result = logger.getModuleOverrides('error')
89
- assert.ok(result.includes('error.myModule'))
90
- assert.ok(result.includes('error.anotherModule'))
91
- assert.strictEqual(result.length, 2)
92
- })
93
-
94
- it('should include negative overrides', () => {
95
- logger.levelsConfig = ['error', '!error.myModule']
96
- const result = logger.getModuleOverrides('error')
97
- assert.ok(result.includes('!error.myModule'))
98
- })
99
-
100
- it('should return empty array when no overrides exist', () => {
101
- logger.levelsConfig = ['error', 'warn']
102
- const result = logger.getModuleOverrides('info')
103
- assert.strictEqual(result.length, 0)
104
- })
105
-
106
- it('should not include overrides for other levels', () => {
107
- logger.levelsConfig = ['error', 'error.moduleA', 'warn.moduleB']
108
- const result = logger.getModuleOverrides('error')
109
- assert.ok(!result.includes('warn.moduleB'))
110
- })
111
- })
112
-
113
- describe('isLoggingEnabled()', () => {
114
- let logger
115
-
116
- beforeEach(() => {
117
- logger = new LoggerModule('test-logger')
118
- logger.config = {
119
- levels: {
120
- error: { enable: true, moduleOverrides: [] },
121
- warn: { enable: false, moduleOverrides: ['warn.specific'] },
122
- info: { enable: true, moduleOverrides: ['!info.blocked'] }
123
- }
124
- }
125
- })
126
-
127
- it('should return true for enabled levels', () => {
128
- assert.strictEqual(logger.isLoggingEnabled('error', 'anyId'), true)
129
- })
130
-
131
- it('should return false for disabled levels without overrides', () => {
132
- assert.strictEqual(logger.isLoggingEnabled('warn', 'generic'), false)
133
- })
134
-
135
- it('should return true for disabled level with positive override', () => {
136
- assert.strictEqual(logger.isLoggingEnabled('warn', 'specific'), true)
137
- })
138
-
139
- it('should return false for enabled level with negative override', () => {
140
- assert.strictEqual(logger.isLoggingEnabled('info', 'blocked'), false)
141
- })
142
-
143
- it('should return true for enabled level without override', () => {
144
- assert.strictEqual(logger.isLoggingEnabled('info', 'allowed'), true)
145
- })
146
-
147
- it('should handle missing level config gracefully', () => {
148
- assert.strictEqual(logger.isLoggingEnabled('nonexistent', 'id'), false)
149
- })
150
- })
151
-
152
- describe('log()', () => {
153
- let logger
154
- let logOutput
155
- let originalConsoleLog
156
-
157
- beforeEach(() => {
158
- logger = new LoggerModule('test-logger')
159
- logger.config = {
160
- levels: {
161
- error: { enable: true, moduleOverrides: [], colour: chalk.red },
162
- warn: { enable: true, moduleOverrides: [], colour: chalk.yellow },
163
- info: { enable: true, moduleOverrides: [], colour: chalk.cyan },
164
- debug: { enable: true, moduleOverrides: [], colour: chalk.dim }
165
- },
166
- timestamp: false,
167
- dateFormat: 'iso',
168
- mute: false
169
- }
170
- logger.logHook = { invoke: () => {} }
171
- logOutput = []
172
-
173
- originalConsoleLog = console.log
174
- console.log = (...args) => logOutput.push({ level: 'log', args })
175
- console.error = (...args) => logOutput.push({ level: 'error', args })
176
- console.warn = (...args) => logOutput.push({ level: 'warn', args })
177
- console.info = (...args) => logOutput.push({ level: 'info', args })
178
- })
179
-
180
- afterEach(() => {
181
- console.log = originalConsoleLog
182
- })
183
-
184
- it('should not log when muted', () => {
185
- logger.config.mute = true
186
- logger.log('info', 'test', 'message')
187
- assert.strictEqual(logOutput.length, 0)
188
- })
189
-
190
- it('should not log when level is disabled', () => {
191
- logger.config.levels.debug.enable = false
192
- logger.log('debug', 'test', 'message')
193
- assert.strictEqual(logOutput.length, 0)
194
- })
195
-
196
- it('should log message when enabled', () => {
197
- logger.log('info', 'testId', 'test message')
198
- assert.strictEqual(logOutput.length, 1)
199
- assert.ok(logOutput[0].args.some(arg => typeof arg === 'string' && arg.includes('testId')))
200
- })
201
-
202
- it('should include multiple arguments', () => {
203
- logger.log('info', 'test', 'arg1', 'arg2', 'arg3')
204
- assert.strictEqual(logOutput.length, 1)
205
- const args = logOutput[0].args
206
- assert.ok(args.includes('arg1'))
207
- assert.ok(args.includes('arg2'))
208
- assert.ok(args.includes('arg3'))
209
- })
210
-
211
- it('should use correct console method for level', () => {
212
- logger.log('error', 'test', 'message')
213
- assert.strictEqual(logOutput[0].level, 'error')
214
- })
215
-
216
- it('should invoke logHook with correct parameters', () => {
217
- let hookArgs = null
218
- logger.logHook.invoke = (...args) => {
219
- hookArgs = args
220
- }
221
- logger.log('info', 'testId', 'message')
222
- assert.ok(hookArgs)
223
- assert.strictEqual(hookArgs[1], 'info')
224
- assert.strictEqual(hookArgs[2], 'testId')
225
- assert.strictEqual(hookArgs[3], 'message')
226
- })
227
-
228
- it('should colorise level in output', () => {
229
- logger.log('info', 'test', 'message')
230
- assert.strictEqual(logOutput.length, 1)
231
- assert.ok(logOutput[0].args[0].includes('info'))
232
- })
233
-
234
- it('should colorise id in output', () => {
235
- logger.log('info', 'myModule', 'message')
236
- assert.strictEqual(logOutput.length, 1)
237
- assert.ok(logOutput[0].args[0].includes('myModule'))
238
- })
239
- })
240
- })