From 4d9b7af79f30d97f77cbdfe868ab7dd133a97217 Mon Sep 17 00:00:00 2001 From: Mohammad Aziz Date: Thu, 2 Oct 2025 08:55:52 +0530 Subject: [PATCH] Set up hlctl project structure --- Makefile | 31 +- cmd/hlctl/commands/root.go | 16 + cmd/hlctl/commands/root_test.go | 41 ++ cmd/hlctl/main.go | 15 + go.mod | 1 + go.sum | 2 + plan.md | 705 +++++++++++++++++++++ test/integration/hlctl_integration_test.go | 44 ++ test/smoke/hlctl_smoke_test.go | 21 + 9 files changed, 874 insertions(+), 2 deletions(-) create mode 100644 cmd/hlctl/commands/root.go create mode 100644 cmd/hlctl/commands/root_test.go create mode 100644 cmd/hlctl/main.go create mode 100644 plan.md create mode 100644 test/integration/hlctl_integration_test.go create mode 100644 test/smoke/hlctl_smoke_test.go diff --git a/Makefile b/Makefile index 9dcb2ce..87ed9e2 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,38 @@ -.PHONY: test start build pushbuild +.PHONY: test test-it test-smoke test-in-docker dev build build-hlctl pushbuild clean + +# Run unit tests test: go test ./... -test-all: + +# Run integration tests +test-it: + go test -tags=integration ./... + +# Run smoke tests +test-smoke: + go test -tags=integration ./test/smoke/... + +# Run tests in Docker +test-in-docker: docker build -f dockers/DockerTestfile -t hostlink-test --progress plain --no-cache --target run-test-stage . + +# Run development server dev: go run ./... + +# Build main binary build: go build -o bin/hostlink + +# Build hlctl binary +build-hlctl: + go build -o bin/hlctl ./cmd/hlctl + +# Build and push to remote server pushbuild: GOOS=linux GOARCH=amd64 go build -o hostlink . && scp -O hostlink hlink:~ + +# Clean build artifacts +clean: + rm -rf bin/ + rm -f cmd/hlctl/hlctl-test diff --git a/cmd/hlctl/commands/root.go b/cmd/hlctl/commands/root.go new file mode 100644 index 0000000..ee735b2 --- /dev/null +++ b/cmd/hlctl/commands/root.go @@ -0,0 +1,16 @@ +package commands + +import ( + "hostlink/version" + + "github.com/urfave/cli/v3" +) + +// NewApp creates the root CLI application +func NewApp() *cli.Command { + return &cli.Command{ + Name: "hlctl", + Usage: "Hostlink CLI - manage tasks and agents", + Version: version.Version, + } +} diff --git a/cmd/hlctl/commands/root_test.go b/cmd/hlctl/commands/root_test.go new file mode 100644 index 0000000..9a1d7a6 --- /dev/null +++ b/cmd/hlctl/commands/root_test.go @@ -0,0 +1,41 @@ +package commands + +import ( + "bytes" + "context" + "testing" + + "hostlink/version" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewApp(t *testing.T) { + app := NewApp() + require.NotNil(t, app) + assert.Equal(t, "hlctl", app.Name) + assert.NotEmpty(t, app.Usage) +} + +func TestAppVersion(t *testing.T) { + app := NewApp() + require.NotNil(t, app) + assert.Equal(t, version.Version, app.Version) +} + +func TestAppHasHelpFlag(t *testing.T) { + app := NewApp() + require.NotNil(t, app) + + var buf bytes.Buffer + app.Writer = &buf + + err := app.Run(context.Background(), []string{"hlctl", "--help"}) + require.NoError(t, err) + + output := buf.String() + assert.Contains(t, output, "hlctl", "Help should contain app name") + assert.Contains(t, output, "Hostlink CLI", "Help should contain usage description") + assert.Contains(t, output, "USAGE", "Help should contain USAGE section") +} diff --git a/cmd/hlctl/main.go b/cmd/hlctl/main.go new file mode 100644 index 0000000..d80ca4c --- /dev/null +++ b/cmd/hlctl/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "context" + "os" + + "hostlink/cmd/hlctl/commands" +) + +func main() { + app := commands.NewApp() + if err := app.Run(context.Background(), os.Args); err != nil { + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index b3f8a7a..1d8fe84 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/urfave/cli/v3 v3.4.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.38.0 // indirect diff --git a/go.sum b/go.sum index 87da493..e07dcae 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= +github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..39534d5 --- /dev/null +++ b/plan.md @@ -0,0 +1,705 @@ +# hlctl MVP Implementation Plan + +## Testing Strategy +- **50% Integration Tests**: End-to-end flows testing CLI → API → Agent → Response +- **20% Smoke Tests**: Basic functionality tests against running server +- **30% Unit Tests**: Individual function/component tests + +## Overview +Build a command-line tool (hlctl) to interact with the Hostlink control plane for task and agent management. + +**Key Features:** +- No authentication (MVP - open API) +- JSON output (modular design for future formats) +- Agent targeting: broadcast to all agents OR filter by fingerprint/tags +- Task creation: inline commands OR script files +- Task and agent management commands + +--- + +## Phase 1: Foundation + +### Task 1: Set up hlctl project structure ✅ + +**Goal**: Create basic CLI application with help and version commands. + +**Files to create:** +- `cmd/hlctl/main.go` +- `cmd/hlctl/commands/root.go` +- `go.mod` updates (add urfave/cli dependency) + +**Success Criteria:** +- [x] `hlctl --help` shows available commands +- [x] `hlctl --version` shows version number +- [x] Project builds successfully: `go build -o hlctl cmd/hlctl/main.go` + +**Tests:** +- **Unit (30%)**: Test command registration and flag parsing ✅ 3/3 passing +- **Integration (50%)**: Test CLI binary execution with --help and --version ✅ 3/3 passing +- **Smoke (20%)**: Manual verification of help output ✅ 1/1 passing + +**Dependencies:** None + +--- + +### Task 2: Configuration management ⏳ + +**Goal**: Support server URL configuration via file and environment variable. + +**Files to create:** +- `cmd/hlctl/config/config.go` +- `cmd/hlctl/config/config_test.go` + +**Success Criteria:** +- [ ] Reads `~/.hostlink/config.yml` if exists +- [ ] Reads `HOSTLINK_SERVER_URL` environment variable +- [ ] Environment variable overrides config file +- [ ] Defaults to `http://localhost:8080` if neither set +- [ ] Config file format: `server: http://example.com` + +**Tests:** +- **Unit (30%)**: Test config loading logic with mocked filesystem +- **Integration (50%)**: Test config file creation and reading +- **Smoke (20%)**: Manual test with different config sources + +**Dependencies:** Task 1 + +--- + +## Phase 2: Control Plane API - Tasks + +### Task 3: Create task creation endpoint ⏳ + +**Goal**: API endpoint to create tasks with optional agent targeting. + +**Files to create/modify:** +- `app/controller/tasks/create.go` +- `app/controller/tasks/create_test.go` +- `domain/task/task.go` (update if needed) +- `config/routes.go` (add route) +- `test/integration/task_api_test.go` + +**API Spec:** +``` +POST /api/v1/tasks +Body: { + "command": "ls -la", + "priority": 1, + "agent_filters": { + "fingerprint": "optional-agent-fingerprint", + "tags": [{"key": "env", "value": "prod"}] + } +} +Response: { + "id": "task-123", + "command": "ls -la", + "status": "pending", + "created_at": "2025-10-02T00:00:00Z" +} +``` + +**Success Criteria:** +- [ ] Creates task without agent_filters (broadcasts to all agents) +- [ ] Creates task with fingerprint filter +- [ ] Creates task with tag filters +- [ ] Returns 400 for invalid request +- [ ] Returns task ID and status in response + +**Tests:** +- **Unit (30%)**: Test request validation, task creation logic +- **Integration (50%)**: Test full HTTP request/response cycle + - Create task without filters + - Create task with fingerprint + - Create task with tags + - Create task with both fingerprint and tags + - Invalid request handling +- **Smoke (20%)**: Test via curl against running server + +**Dependencies:** Task 1, 2 + +--- + +### Task 4: Create task listing endpoint ⏳ + +**Goal**: API endpoint to list tasks with optional filtering. + +**Files to create/modify:** +- `app/controller/tasks/list.go` +- `app/controller/tasks/list_test.go` +- `domain/task/repository.go` (add List method with filters) +- `test/integration/task_api_test.go` (add list tests) + +**API Spec:** +``` +GET /api/v1/tasks?status=pending&agent_id=agent-123 +Response: [ + { + "id": "task-123", + "command": "ls -la", + "status": "pending", + "priority": 1, + "created_at": "2025-10-02T00:00:00Z", + "agent_id": null + } +] +``` + +**Success Criteria:** +- [ ] Lists all tasks without filters +- [ ] Filters by status: `?status=pending` +- [ ] Filters by agent_id: `?agent_id=xxx` +- [ ] Combines multiple filters +- [ ] Returns empty array if no tasks match +- [ ] Returns tasks sorted by created_at DESC + +**Tests:** +- **Unit (30%)**: Test filter logic, query building +- **Integration (50%)**: Test full listing scenarios + - List all tasks + - Filter by status (pending, running, completed, failed) + - Filter by agent_id + - Multiple filters combined + - Empty results +- **Smoke (20%)**: Test via curl against running server + +**Dependencies:** Task 3 + +--- + +### Task 5: Create task details endpoint ⏳ + +**Goal**: API endpoint to get full task details including output. + +**Files to create/modify:** +- `app/controller/tasks/get.go` +- `app/controller/tasks/get_test.go` +- `test/integration/task_api_test.go` (add get tests) + +**API Spec:** +``` +GET /api/v1/tasks/:id +Response: { + "id": "task-123", + "command": "ls -la", + "status": "completed", + "priority": 1, + "agent_id": "agent-456", + "output": "total 48\ndrwxr-xr-x...", + "exit_code": 0, + "created_at": "2025-10-02T00:00:00Z", + "started_at": "2025-10-02T00:00:05Z", + "completed_at": "2025-10-02T00:00:10Z" +} +``` + +**Success Criteria:** +- [ ] Returns full task details for valid task ID +- [ ] Returns 404 for non-existent task ID +- [ ] Includes output and exit_code when available +- [ ] Shows null for pending/running tasks without output + +**Tests:** +- **Unit (30%)**: Test task retrieval logic +- **Integration (50%)**: Test task details scenarios + - Get existing task + - Get non-existent task (404) + - Get task with output + - Get task without output (pending) +- **Smoke (20%)**: Test via curl against running server + +**Dependencies:** Task 4 + +--- + +## Phase 3: Control Plane API - Agents + +### Task 6: Create agent listing endpoint ⏳ + +**Goal**: API endpoint to list all registered agents. + +**Files to create/modify:** +- `app/controller/agents/list.go` +- `app/controller/agents/list_test.go` +- `config/routes.go` (add route) +- `test/integration/agent_api_test.go` + +**API Spec:** +``` +GET /api/v1/agents +Response: [ + { + "id": "agent-123", + "fingerprint": "fp-abc-123", + "status": "active", + "last_seen": "2025-10-02T00:05:00Z", + "tags": [ + {"key": "env", "value": "prod"}, + {"key": "region", "value": "us-east-1"} + ], + "registered_at": "2025-10-01T00:00:00Z" + } +] +``` + +**Success Criteria:** +- [ ] Lists all registered agents +- [ ] Includes agent metadata (fingerprint, tags, status) +- [ ] Shows last_seen timestamp +- [ ] Returns empty array if no agents registered +- [ ] Returns agents sorted by last_seen DESC + +**Tests:** +- **Unit (30%)**: Test agent listing logic +- **Integration (50%)**: Test agent listing scenarios + - List all agents + - List when no agents exist + - Verify tags are included + - Verify sorting by last_seen +- **Smoke (20%)**: Test via curl against running server + +**Dependencies:** Task 5 + +--- + +### Task 7: Create agent details endpoint ⏳ + +**Goal**: API endpoint to get agent details with recent tasks. + +**Files to create/modify:** +- `app/controller/agents/get.go` +- `app/controller/agents/get_test.go` +- `test/integration/agent_api_test.go` (add get tests) + +**API Spec:** +``` +GET /api/v1/agents/:id +Response: { + "id": "agent-123", + "fingerprint": "fp-abc-123", + "status": "active", + "last_seen": "2025-10-02T00:05:00Z", + "tags": [{"key": "env", "value": "prod"}], + "registered_at": "2025-10-01T00:00:00Z", + "recent_tasks": [ + { + "id": "task-456", + "command": "ls -la", + "status": "completed", + "completed_at": "2025-10-02T00:04:00Z" + } + ] +} +``` + +**Success Criteria:** +- [ ] Returns full agent details for valid agent ID +- [ ] Returns 404 for non-existent agent ID +- [ ] Includes recent tasks (last 10) +- [ ] Shows empty array if agent has no tasks + +**Tests:** +- **Unit (30%)**: Test agent retrieval logic +- **Integration (50%)**: Test agent details scenarios + - Get existing agent + - Get non-existent agent (404) + - Get agent with recent tasks + - Get agent without tasks +- **Smoke (20%)**: Test via curl against running server + +**Dependencies:** Task 6 + +--- + +## Phase 4: CLI - Task Commands + +### Task 8: Implement `hlctl task create` ⏳ + +**Goal**: CLI command to create tasks via control plane API. + +**Files to create:** +- `cmd/hlctl/commands/task.go` +- `cmd/hlctl/commands/task_test.go` +- `cmd/hlctl/client/client.go` (HTTP client) +- `cmd/hlctl/client/client_test.go` +- `cmd/hlctl/output/formatter.go` (JSON output formatter) +- `test/integration/hlctl_task_test.go` + +**Command Spec:** +```bash +# Inline command +hlctl task create --command "ls -la" + +# From file +hlctl task create --file script.sh + +# With agent filters +hlctl task create --command "ls" --fingerprint fp-123 +hlctl task create --command "ls" --tag env=prod +hlctl task create --command "ls" --tag env=prod --tag region=us + +# With priority +hlctl task create --command "ls" --priority 5 + +# Output +{"id":"task-123","status":"pending","created_at":"2025-10-02T00:00:00Z"} +``` + +**Success Criteria:** +- [ ] `--command` flag creates task with inline command +- [ ] `--file` flag reads file and creates task +- [ ] Cannot use both `--command` and `--file` (validation error) +- [ ] Must provide either `--command` or `--file` (validation error) +- [ ] `--fingerprint` flag filters by agent fingerprint +- [ ] `--tag` flag filters by tags (repeatable) +- [ ] `--priority` flag sets task priority (default: 1) +- [ ] No filters = broadcasts to all agents +- [ ] Outputs JSON with task ID +- [ ] Shows error message on API failure + +**Tests:** +- **Unit (30%)**: Test flag parsing, file reading, request building +- **Integration (50%)**: Test full CLI → API flow + - Create task with --command + - Create task with --file + - Create task with --fingerprint + - Create task with --tag (single and multiple) + - Create task with --priority + - Create task without filters + - Error: both --command and --file + - Error: neither --command nor --file + - Error: API unreachable +- **Smoke (20%)**: Manual test against running server + +**Dependencies:** Task 3 + +--- + +### Task 9: Implement `hlctl task list` ⏳ + +**Goal**: CLI command to list tasks with optional filters. + +**Files to create/modify:** +- `cmd/hlctl/commands/task.go` (add list subcommand) +- `cmd/hlctl/commands/task_test.go` +- `test/integration/hlctl_task_test.go` (add list tests) + +**Command Spec:** +```bash +# List all tasks +hlctl task list + +# Filter by status +hlctl task list --status pending + +# Filter by agent +hlctl task list --agent agent-123 + +# Multiple filters +hlctl task list --status completed --agent agent-123 + +# Output +[ + {"id":"task-123","command":"ls -la","status":"pending","created_at":"..."} +] +``` + +**Success Criteria:** +- [ ] Lists all tasks without filters +- [ ] `--status` flag filters by status +- [ ] `--agent` flag filters by agent ID +- [ ] Combines multiple filters +- [ ] Outputs JSON array +- [ ] Shows empty array if no tasks match + +**Tests:** +- **Unit (30%)**: Test query parameter building +- **Integration (50%)**: Test full CLI → API flow + - List all tasks + - Filter by status + - Filter by agent + - Multiple filters + - Empty results +- **Smoke (20%)**: Manual test against running server + +**Dependencies:** Task 4, 8 + +--- + +### Task 10: Implement `hlctl task get` ⏳ + +**Goal**: CLI command to get task details. + +**Files to create/modify:** +- `cmd/hlctl/commands/task.go` (add get subcommand) +- `cmd/hlctl/commands/task_test.go` +- `test/integration/hlctl_task_test.go` (add get tests) + +**Command Spec:** +```bash +# Get task details +hlctl task get task-123 + +# Output +{ + "id":"task-123", + "command":"ls -la", + "status":"completed", + "output":"total 48\n...", + "exit_code":0, + "created_at":"...", + "completed_at":"..." +} +``` + +**Success Criteria:** +- [ ] Gets task details for valid task ID +- [ ] Shows error for non-existent task ID +- [ ] Outputs JSON with full details +- [ ] Includes output and exit_code when available + +**Tests:** +- **Unit (30%)**: Test task ID validation +- **Integration (50%)**: Test full CLI → API flow + - Get existing task + - Get non-existent task + - Get task with output + - Get pending task without output +- **Smoke (20%)**: Manual test against running server + +**Dependencies:** Task 5, 8 + +--- + +## Phase 5: CLI - Agent Commands + +### Task 11: Implement `hlctl agent list` ⏳ + +**Goal**: CLI command to list agents. + +**Files to create:** +- `cmd/hlctl/commands/agent.go` +- `cmd/hlctl/commands/agent_test.go` +- `test/integration/hlctl_agent_test.go` + +**Command Spec:** +```bash +# List all agents +hlctl agent list + +# Output +[ + { + "id":"agent-123", + "fingerprint":"fp-abc", + "status":"active", + "tags":[{"key":"env","value":"prod"}], + "last_seen":"..." + } +] +``` + +**Success Criteria:** +- [ ] Lists all registered agents +- [ ] Outputs JSON array +- [ ] Shows empty array if no agents registered +- [ ] Includes tags in output + +**Tests:** +- **Unit (30%)**: Test request building +- **Integration (50%)**: Test full CLI → API flow + - List all agents + - List when no agents exist + - Verify tags included +- **Smoke (20%)**: Manual test against running server + +**Dependencies:** Task 6, 8 + +--- + +### Task 12: Implement `hlctl agent get` ⏳ + +**Goal**: CLI command to get agent details. + +**Files to create/modify:** +- `cmd/hlctl/commands/agent.go` (add get subcommand) +- `cmd/hlctl/commands/agent_test.go` +- `test/integration/hlctl_agent_test.go` (add get tests) + +**Command Spec:** +```bash +# Get agent details +hlctl agent get agent-123 + +# Output +{ + "id":"agent-123", + "fingerprint":"fp-abc", + "status":"active", + "tags":[{"key":"env","value":"prod"}], + "recent_tasks":[ + {"id":"task-456","command":"ls","status":"completed"} + ] +} +``` + +**Success Criteria:** +- [ ] Gets agent details for valid agent ID +- [ ] Shows error for non-existent agent ID +- [ ] Outputs JSON with full details +- [ ] Includes recent tasks + +**Tests:** +- **Unit (30%)**: Test agent ID validation +- **Integration (50%)**: Test full CLI → API flow + - Get existing agent + - Get non-existent agent + - Get agent with tasks + - Get agent without tasks +- **Smoke (20%)**: Manual test against running server + +**Dependencies:** Task 7, 11 + +--- + +## Phase 6: Agent Output Capture + +### Task 13: Ensure agent captures and reports output ⏳ + +**Goal**: Verify agents capture stdout/stderr and send to control plane. + +**Files to verify/modify:** +- `app/services/taskfetcher/taskfetcher.go` (verify output capture) +- Agent task execution code (verify stdout/stderr capture) +- Task completion endpoint (verify accepts output) +- `test/integration/agent_output_test.go` + +**Success Criteria:** +- [ ] Agent captures stdout from command execution +- [ ] Agent captures stderr from command execution +- [ ] Agent sends output to control plane when completing task +- [ ] Control plane stores output in task record +- [ ] Output retrievable via GET /api/v1/tasks/:id +- [ ] Output retrievable via `hlctl task get` + +**Tests:** +- **Unit (30%)**: Test output capture logic in isolation +- **Integration (50%)**: Test full flow + - Create task with command that produces stdout + - Create task with command that produces stderr + - Create task with command that produces both + - Verify output stored in database + - Verify output returned via API + - Verify output shown in hlctl +- **Smoke (20%)**: Manual test with real commands + +**Dependencies:** Task 10 + +--- + +## Phase 7: Integration & Documentation + +### Task 14: End-to-end integration tests ⏳ + +**Goal**: Comprehensive integration tests for complete workflows. + +**Files to create:** +- `test/integration/e2e_hlctl_test.go` + +**Test Scenarios:** +1. **Full task lifecycle:** + - Create task via hlctl + - Agent polls and executes + - Verify output via hlctl task get + +2. **Agent filtering:** + - Register 2 agents with different tags + - Create task targeting specific tag + - Verify only matching agent executes + +3. **Multiple tasks:** + - Create 5 tasks with different priorities + - Verify agents execute in priority order + +4. **Error scenarios:** + - Create task with failing command + - Verify exit_code captured + - Verify stderr captured + +5. **Agent management:** + - List agents shows registered agents + - Get agent shows recent tasks + +6. **File-based task:** + - Create task from script file + - Verify full script executed + - Verify output captured + +**Success Criteria:** +- [ ] All 6 scenarios pass +- [ ] Tests can run against fresh database +- [ ] Tests clean up after themselves +- [ ] Tests document expected behavior + +**Tests:** +- **Integration (100%)**: All tests are integration tests + +**Dependencies:** Task 13 + +--- + +### Task 15: Documentation ⏳ + +**Goal**: Update documentation with hlctl usage and examples. + +**Files to create/modify:** +- `README.md` (add hlctl section) +- `CONTRIBUTING.md` (add hlctl development) +- `docs/hlctl.md` (new - detailed guide) + +**Content to add:** + +1. **README.md:** + - Quick start with hlctl + - Installation instructions + - Basic usage examples + +2. **CONTRIBUTING.md:** + - Building hlctl: `go build -o hlctl cmd/hlctl/main.go` + - Running hlctl tests + - Adding new commands + +3. **docs/hlctl.md:** + - Full command reference + - Examples for common workflows + - Configuration options + - Output format details + +**Success Criteria:** +- [ ] README has hlctl quick start +- [ ] CONTRIBUTING has hlctl dev guide +- [ ] docs/hlctl.md has complete reference +- [ ] All examples tested and working +- [ ] Documentation covers all implemented features + +**Tests:** +- **Smoke (100%)**: Manual verification of all examples + +**Dependencies:** Task 14 + +--- + +## Summary + +**Total Tasks:** 15 +**Estimated Completion:** 15 tasks × focused sessions + +**Testing Breakdown:** +- Integration tests: ~50% (focused on full workflows) +- Smoke tests: ~20% (manual verification against running server) +- Unit tests: ~30% (individual components) + +**Key Milestones:** +- After Task 7: Full REST API complete +- After Task 12: Full CLI complete +- After Task 13: Agent integration complete +- After Task 15: MVP ready for use diff --git a/test/integration/hlctl_integration_test.go b/test/integration/hlctl_integration_test.go new file mode 100644 index 0000000..8f658cb --- /dev/null +++ b/test/integration/hlctl_integration_test.go @@ -0,0 +1,44 @@ +//go:build integration +// +build integration + +package integration + +import ( + "os/exec" + "strings" + "testing" + + "hostlink/version" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHlctlBuild(t *testing.T) { + cmd := exec.Command("go", "build", "-o", "hlctl-test", "../../cmd/hlctl") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Failed to build hlctl: %s", string(output)) + + t.Cleanup(func() { + exec.Command("rm", "-f", "hlctl-test").Run() + }) +} + +func TestHlctlHelp(t *testing.T) { + cmd := exec.Command("go", "run", "../../cmd/hlctl", "--help") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Failed to run hlctl --help: %s", string(output)) + + outputStr := string(output) + assert.Contains(t, outputStr, "hlctl", "Help should contain 'hlctl'") + assert.Contains(t, outputStr, "USAGE", "Help should contain usage section") +} + +func TestHlctlVersion(t *testing.T) { + cmd := exec.Command("go", "run", "../../cmd/hlctl", "--version") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Failed to run hlctl --version: %s", string(output)) + + outputStr := strings.TrimSpace(string(output)) + assert.Contains(t, outputStr, version.Version, "Version output should contain version number") +} diff --git a/test/smoke/hlctl_smoke_test.go b/test/smoke/hlctl_smoke_test.go new file mode 100644 index 0000000..42d07d3 --- /dev/null +++ b/test/smoke/hlctl_smoke_test.go @@ -0,0 +1,21 @@ +//go:build integration +// +build integration + +package smoke + +import ( + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHlctlSmoke(t *testing.T) { + cmd := exec.Command("go", "run", "../../cmd/hlctl") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Failed to run hlctl: %s", string(output)) + + outputStr := string(output) + assert.Contains(t, outputStr, "hlctl", "Default output should contain 'hlctl'") +}