Skip to content

Commit b90e547

Browse files
committed
chore: boost coverage
1 parent 8cf4bdb commit b90e547

2 files changed

Lines changed: 214 additions & 53 deletions

File tree

test/completion/complete.test.ts

Lines changed: 145 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@ function parseOutput (output: string): { candidates: string[]; directive: number
2626
}
2727
}
2828

29+
30+
async function withConfig (yamlLines: string[], fn: () => Promise<void>) {
31+
const { mkdtemp, writeFile, rm } = await import('node:fs/promises')
32+
const { tmpdir } = await import('node:os')
33+
const { join } = await import('node:path')
34+
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-test-'))
35+
const path = join(dir, 'config.yml')
36+
await writeFile(path, yamlLines.join('\n'))
37+
const originalEnv = process.env['ELASTIC_CLI_CONFIG_FILE']
38+
process.env['ELASTIC_CLI_CONFIG_FILE'] = path
39+
try {
40+
await fn()
41+
} finally {
42+
if (originalEnv != null) process.env['ELASTIC_CLI_CONFIG_FILE'] = originalEnv
43+
else delete process.env['ELASTIC_CLI_CONFIG_FILE']
44+
await rm(dir, { recursive: true })
45+
}
46+
}
2947
describe('buildCompletionTree -- top-level commands', () => {
3048
it('registers version + every visible top-level group as stubs by default', async () => {
3149
const root = await buildCompletionTree([])
@@ -101,20 +119,9 @@ describe('buildCompletionTree -- lazy loading', () => {
101119
})
102120

103121
describe('handleComplete -- policy enforcement', () => {
104-
const ORIGINAL_ENV = process.env['ELASTIC_CLI_CONFIG_FILE']
105-
beforeEach(() => { delete process.env['ELASTIC_CLI_CONFIG_FILE'] })
106-
afterEach(() => {
107-
if (ORIGINAL_ENV != null) process.env['ELASTIC_CLI_CONFIG_FILE'] = ORIGINAL_ENV
108-
else delete process.env['ELASTIC_CLI_CONFIG_FILE']
109-
})
110122

111123
it('hides top-level groups blocked by commands.blocked', async () => {
112-
const { mkdtemp, writeFile, rm } = await import('node:fs/promises')
113-
const { tmpdir } = await import('node:os')
114-
const { join } = await import('node:path')
115-
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-policy-'))
116-
const path = join(dir, 'config.yml')
117-
await writeFile(path, [
124+
await withConfig([
118125
'current_context: local',
119126
'commands:',
120127
' blocked:',
@@ -125,10 +132,7 @@ describe('handleComplete -- policy enforcement', () => {
125132
' elasticsearch:',
126133
' url: http://localhost:9200',
127134
'',
128-
].join('\n'))
129-
process.env['ELASTIC_CLI_CONFIG_FILE'] = path
130-
131-
try {
135+
], async () => {
132136
const buf = bufferedWriter()
133137
await handleComplete([''], buf.write)
134138

@@ -139,18 +143,11 @@ describe('handleComplete -- policy enforcement', () => {
139143
// Groups not blocked should still appear.
140144
assert.ok(out.candidates.includes('stack'))
141145
assert.ok(out.candidates.includes('version'))
142-
} finally {
143-
await rm(dir, { recursive: true })
144-
}
146+
})
145147
})
146148

147149
it('applies blocked commands without resolving active-context expressions', async () => {
148-
const { mkdtemp, writeFile, rm } = await import('node:fs/promises')
149-
const { tmpdir } = await import('node:os')
150-
const { join } = await import('node:path')
151-
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-policy-'))
152-
const path = join(dir, 'config.yml')
153-
await writeFile(path, [
150+
await withConfig([
154151
'current_context: local',
155152
'commands:',
156153
' blocked:',
@@ -160,21 +157,17 @@ describe('handleComplete -- policy enforcement', () => {
160157
' elasticsearch:',
161158
' url: $(env:ELASTIC_COMPLETION_MISSING_URL)',
162159
'',
163-
].join('\n'))
164-
process.env['ELASTIC_CLI_CONFIG_FILE'] = path
165-
delete process.env['ELASTIC_COMPLETION_MISSING_URL']
160+
], async () => {
161+
delete process.env['ELASTIC_COMPLETION_MISSING_URL']
166162

167-
try {
168163
const buf = bufferedWriter()
169164
await handleComplete([''], buf.write)
170165

171166
const out = parseOutput(buf.chunks.join(''))
172167
assert.ok(!out.candidates.includes('sanitize'),
173168
`sanitize should be hidden by policy even with unresolved expressions; got: ${out.candidates.join(',')}`)
174169
assert.ok(out.candidates.includes('stack'))
175-
} finally {
176-
await rm(dir, { recursive: true })
177-
}
170+
})
178171
})
179172
})
180173

@@ -222,20 +215,9 @@ describe('handleComplete -- stdout protocol', () => {
222215
})
223216

224217
describe('handleComplete -- dynamic context name completion', () => {
225-
const ORIGINAL_ENV = process.env['ELASTIC_CLI_CONFIG_FILE']
226-
beforeEach(() => { delete process.env['ELASTIC_CLI_CONFIG_FILE'] })
227-
afterEach(() => {
228-
if (ORIGINAL_ENV != null) process.env['ELASTIC_CLI_CONFIG_FILE'] = ORIGINAL_ENV
229-
else delete process.env['ELASTIC_CLI_CONFIG_FILE']
230-
})
231218

232219
it('emits context names from the configured file', async () => {
233-
const { mkdtemp, writeFile, rm } = await import('node:fs/promises')
234-
const { tmpdir } = await import('node:os')
235-
const { join } = await import('node:path')
236-
const dir = await mkdtemp(join(tmpdir(), 'elastic-cli-cnames-'))
237-
const path = join(dir, 'config.yml')
238-
await writeFile(path, [
220+
await withConfig([
239221
'current_context: local',
240222
'contexts:',
241223
' local:',
@@ -245,15 +227,13 @@ describe('handleComplete -- dynamic context name completion', () => {
245227
' elasticsearch:',
246228
' url: http://localhost:9200',
247229
'',
248-
].join('\n'))
249-
process.env['ELASTIC_CLI_CONFIG_FILE'] = path
250-
251-
const buf = bufferedWriter()
252-
await handleComplete(['--use-context', ''], buf.write)
253-
await rm(dir, { recursive: true })
230+
], async () => {
231+
const buf = bufferedWriter()
232+
await handleComplete(['--use-context', ''], buf.write)
254233

255-
const out = parseOutput(buf.chunks.join(''))
256-
assert.deepEqual(out.candidates.sort(), ['local', 'staging'])
234+
const out = parseOutput(buf.chunks.join(''))
235+
assert.deepEqual(out.candidates.sort(), ['local', 'staging'])
236+
})
257237
})
258238
})
259239

@@ -308,3 +288,116 @@ describe('buildCompleteCommand', () => {
308288
assert.match(captured.join(''), /:\d+/)
309289
})
310290
})
291+
292+
describe('buildCompletionTree -- kb lazy loading', () => {
293+
// kb lazy loading exercises the KB_ALIASES branch without importing kb modules that
294+
// would bring uncovered kb/*.ts functions into the coverage scope.
295+
it('shows kb as a stub when secondWord does not match kb aliases', async () => {
296+
const root = await buildCompletionTree(['stack', 'something-else'])
297+
const stack = root.commands.find((c) => c.name() === 'stack')!
298+
const kb = stack.commands.find((c) => c.name() === 'kb')!
299+
assert.ok(kb != null, 'kb should still be present as a stub')
300+
})
301+
})
302+
303+
describe('handleComplete -- loadCompletionCommandPolicy edge cases', () => {
304+
305+
it('returns all top-level groups when current_context is missing from contexts', async () => {
306+
await withConfig([
307+
'current_context: nonexistent',
308+
'contexts:',
309+
' local:',
310+
' elasticsearch:',
311+
' url: http://localhost:9200',
312+
'',
313+
], async () => {
314+
const buf = bufferedWriter()
315+
await handleComplete([''], buf.write)
316+
const out = parseOutput(buf.chunks.join(''))
317+
// policy returns undefined → all commands visible
318+
assert.ok(out.candidates.includes('stack'))
319+
})
320+
})
321+
322+
it('returns all top-level groups when default_profile is invalid', async () => {
323+
await withConfig([
324+
'current_context: local',
325+
'default_profile: 123',
326+
'contexts:',
327+
' local:',
328+
' elasticsearch:',
329+
' url: http://localhost:9200',
330+
'',
331+
], async () => {
332+
const buf = bufferedWriter()
333+
await handleComplete([''], buf.write)
334+
const out = parseOutput(buf.chunks.join(''))
335+
assert.ok(out.candidates.includes('stack'))
336+
})
337+
})
338+
339+
it('returns all top-level groups when root commands block field is invalid', async () => {
340+
await withConfig([
341+
'current_context: local',
342+
'commands:',
343+
' blocked: not-an-array',
344+
'contexts:',
345+
' local:',
346+
' elasticsearch:',
347+
' url: http://localhost:9200',
348+
'',
349+
], async () => {
350+
const buf = bufferedWriter()
351+
await handleComplete([''], buf.write)
352+
const out = parseOutput(buf.chunks.join(''))
353+
assert.ok(out.candidates.includes('stack'))
354+
})
355+
})
356+
357+
it('returns all top-level groups when context commands block field is invalid', async () => {
358+
await withConfig([
359+
'current_context: local',
360+
'contexts:',
361+
' local:',
362+
' commands:',
363+
' blocked: not-an-array',
364+
' elasticsearch:',
365+
' url: http://localhost:9200',
366+
'',
367+
], async () => {
368+
const buf = bufferedWriter()
369+
await handleComplete([''], buf.write)
370+
const out = parseOutput(buf.chunks.join(''))
371+
assert.ok(out.candidates.includes('stack'))
372+
})
373+
})
374+
})
375+
376+
describe('buildCompletionTree -- docs and config subtrees', () => {
377+
it('registers docs as a stub by default', async () => {
378+
const root = await buildCompletionTree(['stack'])
379+
const docs = root.commands.find((c) => c.name() === 'docs')
380+
assert.ok(docs != null, 'docs group should be present')
381+
})
382+
383+
it('registers config as a stub by default', async () => {
384+
const root = await buildCompletionTree(['stack'])
385+
const config = root.commands.find((c) => c.name() === 'config')
386+
assert.ok(config != null, 'config group should be present')
387+
})
388+
389+
it('deep-loads docs when first word is "docs"', async () => {
390+
const root = await buildCompletionTree(['docs'])
391+
const docs = root.commands.find((c) => c.name() === 'docs')
392+
assert.ok(docs != null, 'docs should be present')
393+
// deep-loaded docs should have child commands
394+
assert.ok(docs.commands.length > 0, 'docs should have children when deep-loaded')
395+
})
396+
397+
it('deep-loads config when first word is "config"', async () => {
398+
const root = await buildCompletionTree(['config'])
399+
const config = root.commands.find((c) => c.name() === 'config')
400+
assert.ok(config != null, 'config should be present')
401+
assert.ok(config.commands.length > 0, 'config should have children when deep-loaded')
402+
})
403+
})

test/es/register.test.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { describe, it } from 'node:test'
77
import assert from 'node:assert/strict'
88
import { z } from 'zod'
99
import type { EsApiDefinition } from '../../src/es/types.ts'
10-
import { registerEsCommands } from '../../src/es/register.ts'
10+
import { registerEsCommands, registerEsCommandsLazy } from '../../src/es/register.ts'
1111

1212
function makeDef(name: string, namespace: string, description = `${name} description`): EsApiDefinition {
1313
return { name, namespace, description, method: 'GET', path: `/_${namespace}/${name}` }
@@ -441,3 +441,71 @@ describe('registerEsCommands - built-in API surface', () => {
441441
await assert.rejects(registerEsCommands(defs), /Date cannot be represented in JSON Schema/)
442442
})
443443
})
444+
445+
describe('registerEsCommandsLazy', () => {
446+
it('returns an OpaqueCommandHandle named "es" with no argv sniff match', async () => {
447+
// Pass arbitrary argv that does not target any specific leaf → all stubs
448+
const handle = await registerEsCommandsLazy({ argv: ['node', 'elastic', 'es'] })
449+
assert.equal(handle.name(), 'es')
450+
assert.ok(handle.commands.length > 0, 'should have at least one child (namespace or root stub)')
451+
})
452+
453+
it('contains helpers group as a stub when helpers is not invoked', async () => {
454+
const handle = await registerEsCommandsLazy({ argv: ['node', 'elastic', 'es'] })
455+
const helpers = handle.commands.find((c) => c.name() === 'helpers')
456+
assert.ok(helpers != null, 'should have a helpers command')
457+
})
458+
459+
it('loads helpers group fully when es helpers is invoked', async () => {
460+
const handle = await registerEsCommandsLazy({ argv: ['node', 'elastic', 'es', 'helpers'] })
461+
const helpers = handle.commands.find((c) => c.name() === 'helpers')
462+
assert.ok(helpers != null, 'should have a helpers command')
463+
// When helpers is invoked, the group should be fully populated (> 0 subcommands)
464+
assert.ok(helpers.commands.length > 0, 'helpers should have sub-commands when invoked')
465+
})
466+
467+
it('sniffs a namespaced leaf and expands that namespace fully', async () => {
468+
// cat health is a real leaf in the manifest
469+
const handle = await registerEsCommandsLazy({ argv: ['node', 'elastic', 'es', 'cat', 'health'] })
470+
const cat = handle.commands.find((c) => c.name() === 'cat')
471+
assert.ok(cat != null, 'cat namespace should be present')
472+
// The invoked namespace (cat) should have its leaves fully populated
473+
assert.ok(cat.commands.length > 0, 'cat namespace should have leaf commands when sniffed')
474+
})
475+
476+
it('sniffs a root-level leaf command', async () => {
477+
// 'search' is a root-level command (no namespace)
478+
const handle = await registerEsCommandsLazy({ argv: ['node', 'elastic', 'es', 'search'] })
479+
const search = handle.commands.find((c) => c.name() === 'search')
480+
assert.ok(search != null, 'root-level search command should be present')
481+
})
482+
})
483+
484+
describe('registerEsCommands - responseType and intent', () => {
485+
it('registers formatOutput for text responseType', async () => {
486+
const defs: EsApiDefinition[] = [{
487+
name: 'explain',
488+
description: 'Explain something',
489+
method: 'GET',
490+
path: '/_explain',
491+
responseType: 'text',
492+
}]
493+
// Should register without error and produce a handle
494+
const handle = await registerEsCommands(defs)
495+
const cmd = handle.commands.find((c) => c.name() === 'explain')
496+
assert.ok(cmd != null, 'command should be registered')
497+
})
498+
499+
it('propagates explicit intent override on a definition', async () => {
500+
const defs: EsApiDefinition[] = [{
501+
name: 'reindex',
502+
description: 'Reindex data',
503+
method: 'POST',
504+
path: '/_reindex',
505+
intent: { verb: 'write', object: 'index' },
506+
}]
507+
const handle = await registerEsCommands(defs)
508+
const cmd = handle.commands.find((c) => c.name() === 'reindex')
509+
assert.ok(cmd != null, 'command should be registered with intent')
510+
})
511+
})

0 commit comments

Comments
 (0)