Documentation Index Fetch the complete documentation index at: https://docs.cloudshipai.com/llms.txt
Use this file to discover all available pages before exploring further.
Testing Guide
Station has comprehensive test coverage across unit, integration, and end-to-end tests.
Running Tests
All Tests
# Run all tests
make test
# Or directly with Go
go test ./...
# With verbose output
go test ./... -v
# With race detection
go test ./... -race
Specific Packages
# Test a specific package
go test ./internal/services/... -v
# Test a specific file
go test ./internal/services/agent_service_test.go -v
# Test a specific function
go test ./internal/services -run TestAgentExecution -v
Test Coverage
# Generate coverage report
make test-coverage
# Or manually
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
# View coverage in terminal
go tool cover -func=coverage.out
Current test coverage: ~52.7%
Test Structure
station/
├── cmd/main/handlers/
│ └── agent/
│ ├── execution.go
│ └── execution_test.go # Handler tests
├── internal/
│ ├── services/
│ │ ├── agent_service.go
│ │ └── agent_service_test.go # Service tests
│ ├── api/v1/
│ │ ├── agents.go
│ │ └── agents_test.go # API tests
│ └── db/repositories/
│ ├── agent_repository.go
│ └── agent_repository_test.go # Repository tests
└── tests/
└── integration/ # Integration tests
Writing Tests
Unit Test Pattern
package services
import (
" context "
" testing "
" github.com/stretchr/testify/assert "
" github.com/stretchr/testify/require "
)
func TestAgentService_Execute ( t * testing . T ) {
// Arrange
ctx := context . Background ()
svc := NewAgentService ( mockRepo , mockEngine )
tests := [] struct {
name string
agentID int64
task string
want string
wantErr bool
}{
{
name : "successful execution" ,
agentID : 1 ,
task : "Hello" ,
want : "Response" ,
wantErr : false ,
},
{
name : "agent not found" ,
agentID : 999 ,
task : "Hello" ,
want : "" ,
wantErr : true ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
// Act
got , err := svc . Execute ( ctx , tt . agentID , tt . task )
// Assert
if tt . wantErr {
require . Error ( t , err )
return
}
require . NoError ( t , err )
assert . Equal ( t , tt . want , got )
})
}
}
Table-Driven Tests
Station uses table-driven tests extensively:
func TestValidateCronExpression ( t * testing . T ) {
tests := [] struct {
name string
expression string
valid bool
}{
{ "valid 6-field" , "0 0 9 * * *" , true },
{ "valid 5-field" , "0 9 * * *" , true },
{ "invalid format" , "not-a-cron" , false },
{ "empty" , "" , false },
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
err := ValidateCronExpression ( tt . expression )
if tt . valid {
assert . NoError ( t , err )
} else {
assert . Error ( t , err )
}
})
}
}
Mocking
Station uses interfaces for testability. Generate mocks with:
Example mock usage:
type MockAgentRepository struct {
mock . Mock
}
func ( m * MockAgentRepository ) GetByID ( ctx context . Context , id int64 ) ( * Agent , error ) {
args := m . Called ( ctx , id )
if args . Get ( 0 ) == nil {
return nil , args . Error ( 1 )
}
return args . Get ( 0 ).( * Agent ), args . Error ( 1 )
}
func TestWithMock ( t * testing . T ) {
mockRepo := new ( MockAgentRepository )
mockRepo . On ( "GetByID" , mock . Anything , int64 ( 1 )).
Return ( & Agent { ID : 1 , Name : "test" }, nil )
svc := NewAgentService ( mockRepo )
agent , err := svc . Get ( context . Background (), 1 )
require . NoError ( t , err )
assert . Equal ( t , "test" , agent . Name )
mockRepo . AssertExpectations ( t )
}
Integration Tests
Integration tests use real databases and services:
func TestIntegration_AgentExecution ( t * testing . T ) {
if testing . Short () {
t . Skip ( "Skipping integration test in short mode" )
}
// Setup real database
db , cleanup := setupTestDB ( t )
defer cleanup ()
// Create real services
repos := repositories . New ( db )
svc := services . NewAgentService ( repos )
// Run integration test
// ...
}
Run integration tests:
# Run all tests including integration
go test ./... -v
# Skip integration tests
go test ./... -short
API Tests
func TestAPI_CreateAgent ( t * testing . T ) {
// Setup test server
router := setupTestRouter ()
// Create request
body := `{"name": "test-agent", "prompt": "You are helpful"}`
req := httptest . NewRequest ( "POST" , "/api/v1/agents" , strings . NewReader ( body ))
req . Header . Set ( "Content-Type" , "application/json" )
req . Header . Set ( "Authorization" , "Bearer test-token" )
// Execute
w := httptest . NewRecorder ()
router . ServeHTTP ( w , req )
// Assert
assert . Equal ( t , http . StatusCreated , w . Code )
var response map [ string ] interface {}
err := json . Unmarshal ( w . Body . Bytes (), & response )
require . NoError ( t , err )
assert . Equal ( t , "test-agent" , response [ "name" ])
}
Database Tests
func TestRepository_AgentCRUD ( t * testing . T ) {
// Use in-memory SQLite for tests
db , err := sql . Open ( "sqlite3" , ":memory:" )
require . NoError ( t , err )
defer db . Close ()
// Run migrations
_ , err = db . Exec ( schema )
require . NoError ( t , err )
repo := NewAgentRepository ( db )
// Test Create
agent := & Agent { Name : "test" , Prompt : "Hello" }
id , err := repo . Create ( context . Background (), agent )
require . NoError ( t , err )
assert . Greater ( t , id , int64 ( 0 ))
// Test Read
found , err := repo . GetByID ( context . Background (), id )
require . NoError ( t , err )
assert . Equal ( t , "test" , found . Name )
// Test Update
found . Name = "updated"
err = repo . Update ( context . Background (), found )
require . NoError ( t , err )
// Test Delete
err = repo . Delete ( context . Background (), id )
require . NoError ( t , err )
}
Test Helpers
Test Database Setup
func setupTestDB ( t * testing . T ) ( * sql . DB , func ()) {
t . Helper ()
// Create temp file
f , err := os . CreateTemp ( "" , "station-test-*.db" )
require . NoError ( t , err )
f . Close ()
// Open database
db , err := sql . Open ( "sqlite3" , f . Name ())
require . NoError ( t , err )
// Run migrations
_ , err = db . Exec ( schema . SQL )
require . NoError ( t , err )
cleanup := func () {
db . Close ()
os . Remove ( f . Name ())
}
return db , cleanup
}
Test Fixtures
func createTestAgent ( t * testing . T , repo AgentRepository ) * Agent {
t . Helper ()
agent := & Agent {
Name : "test-agent-" + randomString ( 8 ),
Prompt : "You are a test agent" ,
MaxSteps : 5 ,
Environment : "default" ,
}
id , err := repo . Create ( context . Background (), agent )
require . NoError ( t , err )
agent . ID = id
return agent
}
Continuous Integration
Tests run automatically on:
Every push to main
Every pull request
Release tag creation
CI configuration runs:
# .github/workflows/test.yml
jobs :
test :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : actions/setup-go@v5
with :
go-version : '1.21'
- run : make test
- run : make lint
Testing Best Practices
Test behavior, not implementation - Focus on what the code does, not how
Use table-driven tests - Makes adding test cases easy
Mock at boundaries - Mock external services, not internal code
Keep tests fast - Use in-memory databases, skip slow tests with -short
Test error cases - Ensure errors are handled correctly
Clean up resources - Use defer for cleanup
Use meaningful names - Test names should describe the scenario
Next Steps
Code Style Coding conventions and standards
Architecture Understanding the codebase structure