-
Notifications
You must be signed in to change notification settings - Fork 2
Epic: Phase 2 - Testing Infrastructure #2
Copy link
Copy link
Closed
Description
Phase 2: Testing Infrastructure
Priority: P0 - Critical for safe refactoring
Estimated Sub-tasks: 4
Blocked By: Phase 1 (Foundation)
Summary
Add comprehensive test coverage before any major refactoring. This includes unit tests for the API client, credential storage, and command handlers, plus a manual integration test specification.
Current State: 0 test files, 0% coverage across 1,617 lines of code.
Sub-Tasks
1. Add Unit Tests for API Client
- Create
internal/client/client_test.go - Add constructor for test injection:
NewClientWithBaseURL(baseURL, token string) - Test client creation and configuration
- Test authentication header injection
- Test successful GET/POST requests
- Test error handling (4xx, 5xx responses)
- Test Slack API error parsing (
{"ok": false, "error": "..."}) - Test pagination handling for list endpoints
- Achieve >80% coverage for client.go
Implementation Pattern:
func TestClient_ListChannels_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method)
assert.Contains(t, r.URL.Path, "conversations.list")
auth := r.Header.Get("Authorization")
assert.True(t, strings.HasPrefix(auth, "Bearer "))
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"ok": true, "channels": [{"id": "C123", "name": "general"}]}`))
}))
defer server.Close()
client := NewClientWithBaseURL(server.URL, "test-token")
channels, err := client.ListChannels("", true, 100)
require.NoError(t, err)
assert.Len(t, channels, 1)
assert.Equal(t, "C123", channels[0].ID)
}
func TestClient_APIError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"ok": false, "error": "channel_not_found"}`))
}))
defer server.Close()
client := NewClientWithBaseURL(server.URL, "test-token")
_, err := client.GetChannelInfo("C999")
require.Error(t, err)
assert.Contains(t, err.Error(), "channel_not_found")
}
func TestClient_Pagination(t *testing.T) {
callCount := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
callCount++
if callCount == 1 {
w.Write([]byte(`{"ok": true, "channels": [{"id": "C1"}], "response_metadata": {"next_cursor": "abc"}}`))
} else {
w.Write([]byte(`{"ok": true, "channels": [{"id": "C2"}], "response_metadata": {"next_cursor": ""}}`))
}
}))
defer server.Close()
client := NewClientWithBaseURL(server.URL, "test-token")
channels, err := client.ListChannels("", true, 100)
require.NoError(t, err)
assert.Len(t, channels, 2)
assert.Equal(t, 2, callCount)
}Test Cases:
| Test Case | Expected |
|---|---|
| ListChannels success | Returns channel list |
| ListChannels with pagination | Follows cursor, returns all |
| GetChannelInfo success | Returns single channel |
| GetChannelInfo not found | Returns error with message |
| SendMessage success | Returns message with timestamp |
| Network error | Returns wrapped error |
| Invalid JSON response | Returns parse error |
2. Add Unit Tests for Keychain/Credential Storage
- Create
internal/keychain/keychain_test.go - Test
GetAPIToken()when token exists - Test
GetAPIToken()when token doesn't exist - Test
GetAPIToken()withSLACK_API_TOKENenv var override - Test
SetAPIToken()stores token correctly - Test
DeleteAPIToken()removes token - Test
IsSecureStorage()returns correct value - Test file permissions on Linux (0600)
- Use
t.TempDir()for file-based tests
Implementation Pattern:
func TestGetAPIToken_FromEnvVar(t *testing.T) {
t.Setenv("SLACK_API_TOKEN", "xoxb-test-token")
token, err := GetAPIToken()
require.NoError(t, err)
assert.Equal(t, "xoxb-test-token", token)
}
func TestSetAPIToken_FilePermissions(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skip("File-based storage only on Linux")
}
tmpDir := t.TempDir()
oldConfigDir := configDir
configDir = tmpDir
defer func() { configDir = oldConfigDir }()
err := SetAPIToken("test-token")
require.NoError(t, err)
credPath := filepath.Join(tmpDir, "credentials")
info, _ := os.Stat(credPath)
assert.Equal(t, os.FileMode(0600), info.Mode().Perm())
}
func TestGetAPIToken_NotConfigured(t *testing.T) {
t.Setenv("SLACK_API_TOKEN", "")
// ... setup to ensure no stored token
_, err := GetAPIToken()
require.Error(t, err)
assert.Contains(t, err.Error(), "no API token found")
}3. Add Unit Tests for Command Handlers
- Refactor commands to accept injectable API client
- Refactor commands to accept injectable stdin (for confirmations)
- Create
cmd/channels_test.go - Create
cmd/users_test.go - Create
cmd/messages_test.go - Create
cmd/workspace_test.go - Create
cmd/config_test.go - Test happy path for each command
- Test error handling for each command
- Test flag parsing and validation
- Test output formatting (JSON vs text)
Implementation Pattern:
func TestChannelsList_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"ok": true, "channels": [{"id": "C123", "name": "general", "num_members": 10}]}`))
}))
defer server.Close()
var stdout, stderr bytes.Buffer
cmd := NewCmdChannelsList()
cmd.SetOut(&stdout)
cmd.SetErr(&stderr)
// Inject test client
testClient := client.NewClientWithBaseURL(server.URL, "test-token")
runListWithClient = func(opts *listOptions) error {
return runList(opts, testClient)
}
err := cmd.Execute()
require.NoError(t, err)
assert.Contains(t, stdout.String(), "general")
}
func TestChannelsList_JSONOutput(t *testing.T) {
// ... setup server ...
cmd := NewCmdChannelsList()
cmd.SetArgs([]string{"--json"})
err := cmd.Execute()
require.NoError(t, err)
var result []client.Channel
err = json.Unmarshal(stdout.Bytes(), &result)
require.NoError(t, err)
assert.Len(t, result, 1)
}4. Create integration-tests.md Manual Test Suite
- Create
integration-tests.mdat project root - Document test environment setup
- Document test data conventions
- Create test matrix for each command group
- Include edge cases section
- Include test execution checklist
Content Structure:
# Integration Tests
Manual tests for verifying real-world behavior against live Slack API.
## Test Environment Setup
### Prerequisites
- Slack workspace with test channel
- Bot token with required scopes (see slack-app-manifest.yaml)
- Permission to create/delete channels
### Test Data Conventions
- Test channels use `[Test]` prefix in name
- Clean up test data after tests
## Command Tests
### channels list
| Test Case | Command | Expected Result |
|-----------|---------|-----------------|
| List all | `slack-cli channels list` | Shows table of channels |
| JSON output | `slack-cli channels list --json` | Valid JSON array |
| Filter public | `slack-cli channels list --types=public_channel` | Only public channels |
| With limit | `slack-cli channels list --limit 5` | Max 5 results |
### messages send
| Test Case | Command | Expected Result |
|-----------|---------|-----------------|
| Send text | `slack-cli messages send #test "Hello"` | Message appears in channel |
| Send in thread | `slack-cli messages send #test "Reply" --thread 1234.5678` | Reply in thread |
| Send with blocks | `slack-cli messages send #test "Text" --blocks '[...]'` | Block Kit message |
[... continue for all commands ...]
## Edge Cases
| Test Case | Expected Result |
|-----------|-----------------|
| Unicode in channel names | Handled correctly |
| Empty results | Graceful "No channels found" message |
| Invalid channel ID | Clear error message |
| Rate limiting | Error message with retry info |
## Test Execution Checklist
- [ ] Build latest: `make build`
- [ ] Configure token: `slack-cli config set-token`
- [ ] Create test channel: `slack-cli channels create [Test]-integration`
- [ ] Run through all command tests
- [ ] Test edge cases
- [ ] Clean up: `slack-cli channels archive [Test]-integration`Acceptance Criteria
-
make testruns all tests with race detection -
make test-covershows >70% overall coverage - Client package has >80% coverage
- All commands have at least happy-path tests
- integration-tests.md documents all commands
Dependencies
- Blocked By: Phase 1 (CI must exist to run tests)
- Blocks: Phase 3 (need tests before major refactoring)
Files to Create
internal/client/client_test.gointernal/keychain/keychain_test.gocmd/channels_test.gocmd/users_test.gocmd/messages_test.gocmd/workspace_test.gocmd/config_test.gointegration-tests.md
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels