Files
spoon/apps/agent-worker/tests/unit/box-user.test.ts
T
Gabriel Brown 4505471149 fix(worker): keep-id compat, zombie tmux clients, nvm in box image
- Box init + chownInBox exec explicitly as root: under podman keep-id the
  container's default user is the mapped uid, which cannot useradd/chown.
- Init renames an existing uid-1000 passwd entry (podman keep-id injects one
  named after the HOST user) instead of adding a duplicate that would win the
  getpwuid lookup and make whoami report the wrong name.
- Terminal connections tag their shell with a per-connection marker and reap
  the process group on WebSocket close: closing the attach socket never
  killed the exec'd shell, so every disconnect leaked an attached tmux client
  (session stuck at the smallest zombie's size; enough dead clients starve
  tmux rendering for live ones).
- Job image: nvm 0.39.5 at /usr/local/nvm (user-writable) with
  /etc/profile.d/nvm.sh, matching what user dotfiles expect; docs for the box
  user + ownership model.
2026-07-13 10:36:44 -04:00

86 lines
2.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
buildBoxInitScript,
buildClearPasswordCommand,
buildSudoPolicyScript,
linuxUsername,
} from '../../src/runtime/box-user';
describe('linuxUsername', () => {
it('lowercases and passes through simple names', () => {
expect(linuxUsername('gabriel')).toBe('gabriel');
expect(linuxUsername('Gabriel')).toBe('gabriel');
});
it('replaces invalid characters with dashes', () => {
expect(linuxUsername('g@b r!el')).toBe('g-b-r-el');
});
it('prefixes names that do not start with [a-z_]', () => {
expect(linuxUsername('1337')).toBe('u1337');
expect(linuxUsername('-dash')).toBe('u-dash');
});
it('truncates to 32 chars and falls back to spoon', () => {
expect(linuxUsername('a'.repeat(40))).toHaveLength(32);
expect(linuxUsername('')).toBe('spoon');
expect(linuxUsername('@@@')).toBe('u---');
});
});
describe('buildBoxInitScript', () => {
const script = buildBoxInitScript({
username: 'gabriel',
home: '/home/Gabriel',
});
it('renames an existing uid-1000 entry instead of duplicating it', () => {
// podman --userns=keep-id injects a passwd entry named after the HOST user
// at the mapped uid; a second uid-1000 entry would win the getpwuid lookup
// and make whoami report the host username.
expect(script).toContain('getent passwd 1000');
expect(script).toContain("usermod -l 'gabriel'");
});
it('creates the user with uid 1000 at the given home when none exists', () => {
expect(script).toContain('useradd');
expect(script).toContain('-u 1000');
expect(script).toContain("-d '/home/Gabriel'");
expect(script).toContain('-s /bin/bash');
});
it('adds wheel membership', () => {
expect(script).toContain("usermod -aG wheel 'gabriel'");
});
it('chowns the home once, guarded by the marker', () => {
expect(script).toContain('/home/Gabriel/.spoon/chown-v1');
expect(script).toContain("chown -R 1000:1000 '/home/Gabriel'");
});
});
describe('buildSudoPolicyScript', () => {
it('installs a visudo-validated NOPASSWD drop-in when no password is set', () => {
const script = buildSudoPolicyScript({
username: 'gabriel',
passwordSet: false,
});
expect(script).toContain('gabriel ALL=(ALL) NOPASSWD:ALL');
expect(script).toContain('visudo -c');
expect(script).toContain('/etc/sudoers.d/spoon-box');
expect(script).toContain('chmod 0440');
});
it('removes the drop-in when a password is set', () => {
const script = buildSudoPolicyScript({
username: 'gabriel',
passwordSet: true,
});
expect(script).toContain('rm -f /etc/sudoers.d/spoon-box');
expect(script).not.toContain('NOPASSWD');
});
});
describe('password commands', () => {
it('clears via passwd -d', () => {
expect(buildClearPasswordCommand('gabriel')).toEqual([
'passwd',
'-d',
'gabriel',
]);
});
});