feat: add CI test workflow and commands tests (#147)

Testing infrastructure improvements:

1. Add GitHub Actions workflow (.github/workflows/test.yml)
   - Runs on push/PR to main
   - Installs deps, builds, runs tests
   - Blocks merging broken code

2. Add tests for commands.ts (src/core/commands.test.ts)
   - Tests parseCommand() with valid/invalid inputs
   - Tests case insensitivity
   - Tests COMMANDS array and HELP_TEXT

Now at 231 tests across 17 test files.

Written by Cameron ◯ Letta Code

"Test early, test often." - Software proverb
This commit is contained in:
Cameron
2026-02-04 17:13:18 -08:00
committed by GitHub
parent c8d55c8e84
commit 030a2b2bc5
2 changed files with 113 additions and 0 deletions

27
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Run tests
run: npm run test:run

86
src/core/commands.test.ts Normal file
View File

@@ -0,0 +1,86 @@
import { describe, it, expect } from 'vitest';
import { parseCommand, COMMANDS, HELP_TEXT } from './commands.js';
describe('parseCommand', () => {
describe('valid commands', () => {
it('returns "status" for /status', () => {
expect(parseCommand('/status')).toBe('status');
});
it('returns "heartbeat" for /heartbeat', () => {
expect(parseCommand('/heartbeat')).toBe('heartbeat');
});
it('returns "help" for /help', () => {
expect(parseCommand('/help')).toBe('help');
});
it('returns "start" for /start', () => {
expect(parseCommand('/start')).toBe('start');
});
});
describe('invalid input', () => {
it('returns null for non-slash messages', () => {
expect(parseCommand('hello')).toBeNull();
expect(parseCommand('status')).toBeNull();
});
it('returns null for empty string', () => {
expect(parseCommand('')).toBeNull();
});
it('returns null for null/undefined', () => {
expect(parseCommand(null)).toBeNull();
expect(parseCommand(undefined)).toBeNull();
});
it('returns null for unknown commands', () => {
expect(parseCommand('/unknown')).toBeNull();
expect(parseCommand('/foo')).toBeNull();
expect(parseCommand('/stats')).toBeNull(); // Similar but not exact
});
});
describe('command parsing', () => {
it('handles commands with extra text after', () => {
expect(parseCommand('/status please')).toBe('status');
expect(parseCommand('/help me')).toBe('help');
});
it('is case insensitive', () => {
expect(parseCommand('/STATUS')).toBe('status');
expect(parseCommand('/Help')).toBe('help');
expect(parseCommand('/HEARTBEAT')).toBe('heartbeat');
});
it('handles commands with leading/trailing whitespace in args', () => {
expect(parseCommand('/status ')).toBe('status');
});
});
});
describe('COMMANDS', () => {
it('contains all expected commands', () => {
expect(COMMANDS).toContain('status');
expect(COMMANDS).toContain('heartbeat');
expect(COMMANDS).toContain('help');
expect(COMMANDS).toContain('start');
});
it('has exactly 4 commands', () => {
expect(COMMANDS).toHaveLength(4);
});
});
describe('HELP_TEXT', () => {
it('contains command descriptions', () => {
expect(HELP_TEXT).toContain('/status');
expect(HELP_TEXT).toContain('/heartbeat');
expect(HELP_TEXT).toContain('/help');
});
it('contains LettaBot branding', () => {
expect(HELP_TEXT).toContain('LettaBot');
});
});