feat: implement reserved service name validation for Apl services - #1074
feat: implement reserved service name validation for Apl services#1074CasLubbers wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Implements validation to prevent creation/update of APL team services whose names are reserved due to collisions with per-team platform hostnames.
Changes:
- Added
RESERVED_SERVICE_NAMESenvironment validator with a default reserved list. - Enforced reserved-name validation in
createAplServiceandeditAplService. - Added Jest coverage for reserved-name behavior and env overrides; updated
package-lock.json.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/validators.ts | Adds a new env validator for RESERVED_SERVICE_NAMES. |
| src/otomi-stack.ts | Parses reserved names and rejects reserved service names on create/edit. |
| src/otomi-stack.test.ts | Adds tests for reserved-name validation and env customization. |
| package-lock.json | Lockfile metadata updates (removed peer flags in multiple entries). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| function getReservedServiceNames(): Set<string> { | ||
| return new Set( | ||
| env.RESERVED_SERVICE_NAMES.split(',') | ||
| .map((name) => name.trim().toLowerCase()) | ||
| .filter((name) => name.length > 0), | ||
| ) | ||
| } |
| if (reserved.has(name.trim().toLowerCase())) { | ||
| throw new ValidationError(`Service name is reserved. Reserved names: ${Array.from(reserved).join(', ')}`) | ||
| } |
| it('trims whitespace and ignores case', () => { | ||
| let assertServiceNameNotReserved: typeof import('./serviceUtils').assertServiceNameNotReserved | ||
| jest.isolateModules(() => { | ||
| ;({ assertServiceNameNotReserved } = require('./serviceUtils')) | ||
| }) |
| it('allows a non-reserved name on create', async () => { | ||
| await expect(otomiStack.createAplService(teamId, buildService('my-service'))).resolves.not.toThrow() |
There was a problem hiding this comment.
🔵 Needs a closer look
A newly added unit test is environment-dependent and can become flaky unless it explicitly controls process.env.RESERVED_SERVICE_NAMES.
Review details
Suppressed comments (1)
src/utils/serviceUtils.test.ts:46
- The "trims whitespace and ignores case" test relies on whatever RESERVED_SERVICE_NAMES happens to be in the test runner environment. If CI (or another suite) sets it to a value that doesn’t include grafana, this test will fail even though the implementation is correct. Make the test deterministic by explicitly deleting the env var (so the validator default applies) before requiring the module.
it('trims whitespace and ignores case', () => {
let assertServiceNameNotReserved: typeof import('./serviceUtils').assertServiceNameNotReserved
jest.isolateModules(() => {
;({ assertServiceNameNotReserved } = require('./serviceUtils'))
})
- Files reviewed: 5/6 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
The new tests include a Jest matcher misuse (resolves.not.toThrow()) and one unit test is environment-dependent unless RESERVED_SERVICE_NAMES is explicitly controlled.
Review details
Suppressed comments (2)
src/utils/serviceUtils.test.ts:45
- In this test, RESERVED_SERVICE_NAMES is not set/cleared before loading ./serviceUtils, so the assertion can become environment-dependent if RESERVED_SERVICE_NAMES is defined externally when the suite runs. Set or delete the env var explicitly (like the other tests in this file) before isolateModules to keep the test deterministic.
it('trims whitespace and ignores case', () => {
let assertServiceNameNotReserved: typeof import('./serviceUtils').assertServiceNameNotReserved
jest.isolateModules(() => {
;({ assertServiceNameNotReserved } = require('./serviceUtils'))
})
src/otomi-stack.test.ts:277
resolves.not.toThrow()is a matcher misuse:toThrowexpects a function, so this assertion can fail with "received value must be a function" even when the promise resolves successfully. Assert that the promise resolves (and optionally check the returned value) instead.
it('allows a non-reserved name on create', async () => {
await expect(otomiStack.createAplService(teamId, buildService('my-service'))).resolves.not.toThrow()
})
- Files reviewed: 5/6 changed files
- Comments generated: 0 new
- Review effort level: Lite
No description provided.