mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-29 00:01:22 +00:00
feat(service): variables editor with masked-secret preservation
PUT /services/{id}/variables replaced every row, and GET masks secrets as
******** — echoing the fetched list back would have overwritten real
secrets with asterisks. The backend now keeps the stored value when a
submitted secret still carries the mask.
Service detail gains a Variables section: add/edit/remove rows, secret
toggle, bulk .env paste (comments, export prefix, quoted values), and
inline validation for empty/invalid/duplicate keys. Parser and validator
live in variable-utils.ts with unit coverage.
Generated OpenAPI types drive the new updateServiceVariables client call.
ROADMAP: Variables UX marked done; per-environment values are already
structural — each environment owns its service instances.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
e6e76c1084
commit
1d5b99e1ba
6 files changed
+354
-13
No files matched your search
@@ -0,0 +1,40 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseDotenv, validateVariableRows } from './variable-utils';
|
||||
|
||||
describe('parseDotenv', () => {
|
||||
it('parses KEY=VAL lines and skips blanks/comments', () => {
|
||||
const rows = parseDotenv('A=1\n\n# comment\nB=two\n');
|
||||
expect(rows).toEqual([
|
||||
{ key: 'A', value: '1', isSecret: false },
|
||||
{ key: 'B', value: 'two', isSecret: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('strips export prefix and quotes, keeps = inside values', () => {
|
||||
const rows = parseDotenv('export A="x=y"\nB=\' spaced \'\nC=plain\n');
|
||||
expect(rows.map((r) => [r.key, r.value])).toEqual([
|
||||
['A', 'x=y'],
|
||||
['B', ' spaced '],
|
||||
['C', 'plain'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignores malformed lines without a key', () => {
|
||||
expect(parseDotenv('=novalue\njusttext\n')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateVariableRows', () => {
|
||||
it('flags empty, invalid, and duplicate keys', () => {
|
||||
const errors = validateVariableRows([
|
||||
{ key: 'OK', value: '1', isSecret: false },
|
||||
{ key: '', value: 'x', isSecret: false },
|
||||
{ key: '9BAD', value: 'x', isSecret: false },
|
||||
{ key: 'OK', value: '2', isSecret: false },
|
||||
]);
|
||||
expect(errors[0]).toBeUndefined();
|
||||
expect(errors[1]).toBe('Key is required');
|
||||
expect(errors[2]).toContain('Letters, digits');
|
||||
expect(errors[3]).toBe('Duplicate key');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user