feat: add build system and integration tests
- Update Makefile with test-coverage and verify targets - Add build.sh script for cross-platform builds with SHA256 checksums - Implement integration tests for full workflow (add/list/get/update/export/delete) - Add config integration test - Update project state documentation
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
.PHONY: build run test clean lint fmt vet install release help
|
||||
.PHONY: build run test test-short test-coverage clean fmt vet lint install tidy deps verify release help
|
||||
|
||||
# Binary name
|
||||
BINARY_NAME=hostkeeper
|
||||
@@ -37,10 +37,17 @@ test:
|
||||
test-short:
|
||||
$(GOTEST) ./... -v -short
|
||||
|
||||
## test-coverage: Run tests with coverage report
|
||||
test-coverage:
|
||||
$(GOTEST) ./... -v -coverprofile=coverage.out -covermode=atomic
|
||||
$(GOCMD) tool cover -html=coverage.out -o coverage.html
|
||||
@echo "Coverage report generated: coverage.html"
|
||||
|
||||
## clean: Remove build artifacts
|
||||
clean:
|
||||
$(GOCLEAN)
|
||||
rm -rf $(BUILD_DIR)
|
||||
rm -f coverage.out coverage.html
|
||||
|
||||
## fmt: Format all Go files
|
||||
fmt:
|
||||
@@ -66,6 +73,10 @@ tidy:
|
||||
deps:
|
||||
$(GOCMD) mod download
|
||||
|
||||
## verify: Verify module dependencies
|
||||
verify:
|
||||
$(GOCMD) mod verify
|
||||
|
||||
## release: Build for multiple platforms
|
||||
release: clean
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
+16
-6
@@ -2,7 +2,7 @@
|
||||
|
||||
> **Purpose**: Enable seamless continuation of development by any agent/LLM across sessions
|
||||
>
|
||||
> **Last Updated**: 2024-06-23 (Session 8)
|
||||
> **Last Updated**: 2024-06-23 (Session 9)
|
||||
> **Current Status**: Implementation In Progress - Tasks 1-8 Complete
|
||||
> **Phase**: MVP Development (Phase 1)
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
✅ **Bug Fix**: Fixed deadlock in JSON storage (RLock within Lock)
|
||||
|
||||
### What Needs to Happen Next
|
||||
🔄 **Task 12**: Build and Testing (Makefile, integration tests)
|
||||
🔄 **Task 13**: Documentation (README, usage docs)
|
||||
🔄 **Task 14**: Final testing and release prep
|
||||
|
||||
@@ -54,10 +53,10 @@
|
||||
| **CLI Framework** | ✅ 100% | Cobra root, version, completion commands |
|
||||
| **CLI Commands** | 🟡 70% | add + list + connect + edit + delete + export + import done |
|
||||
| **TUI** | 🟡 40% | Basic TUI with host list navigation |
|
||||
| **Testing** | 🟡 55% | Error + SSH + add + list + connect + edit + delete + TUI + export/import tests passing |
|
||||
| **Testing** | 🟡 60% | All unit + integration tests passing |
|
||||
| **Documentation** | 🔲 0% | Usage guides and API docs |
|
||||
|
||||
### Overall Progress: **~75% Complete** (Tasks 1-11 done)
|
||||
### Overall Progress: **~80% Complete** (Tasks 1-12 done)
|
||||
|
||||
---
|
||||
|
||||
@@ -177,7 +176,16 @@
|
||||
- `cmd/hostkeeper/import.go` — Import command with replace/merge strategies and dry-run preview
|
||||
- `test/storage/export_import_test.go` — Integration test for export/import round-trip
|
||||
|
||||
#### 🔲 Task 12-14: Remaining Tasks
|
||||
#### ✅ Task 12: Build and Testing
|
||||
- **Status**: ✅ Completed
|
||||
- **Priority**: HIGH
|
||||
- **Deliverables**: Build system and integration tests
|
||||
- **Files Created/Modified**:
|
||||
- `Makefile` — Added test-coverage, verify targets; updated clean to remove coverage files
|
||||
- `build.sh` — Cross-platform build script with SHA256 checksums
|
||||
- `test/integration/integration_test.go` — End-to-end workflow tests (add, list, get, update, export/import, delete, config)
|
||||
|
||||
#### 🔲 Task 13-14: Remaining Tasks
|
||||
- **Status**: Not Started
|
||||
- **Details**: See `docs/plans/2024-06-22-hostkeeper-implementation.md`
|
||||
|
||||
@@ -186,7 +194,7 @@
|
||||
## 🗺️ Development Roadmap
|
||||
|
||||
### Current Week Focus
|
||||
**Target**: Complete Tasks 10+ (TUI, Export/Import)
|
||||
**Target**: Complete Tasks 12+ (Build, Testing, Docs, Release)
|
||||
|
||||
### This Sprint
|
||||
- [x] Project setup and dependencies
|
||||
@@ -201,6 +209,8 @@
|
||||
- [x] Edit host command
|
||||
- [x] Delete host command
|
||||
- [x] TUI implementation
|
||||
- [x] Export/Import commands
|
||||
- [x] Build system and integration tests
|
||||
|
||||
### Next Sprint
|
||||
- [ ] TUI implementation (Bubble Tea)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
# Build script for multiple platforms
|
||||
set -e
|
||||
|
||||
VERSION=${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo "dev")}
|
||||
BUILD_DIR=${BUILD_DIR:-"build"}
|
||||
BINARY_NAME="hostkeeper"
|
||||
|
||||
echo "Building Hostkeeper v${VERSION}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
rm -rf ${BUILD_DIR}
|
||||
mkdir -p ${BUILD_DIR}
|
||||
|
||||
platforms=(
|
||||
"linux/amd64"
|
||||
"linux/arm64"
|
||||
"linux/arm"
|
||||
"darwin/amd64"
|
||||
"darwin/arm64"
|
||||
"windows/amd64"
|
||||
)
|
||||
|
||||
for platform in "${platforms[@]}"; do
|
||||
IFS='/' read -r os arch <<< "$platform"
|
||||
|
||||
output_name="${BINARY_NAME}-${os}-${arch}"
|
||||
if [ "$os" = "windows" ]; then
|
||||
output_name="${output_name}.exe"
|
||||
fi
|
||||
|
||||
echo " Building for ${os}/${arch}..."
|
||||
|
||||
GOOS=$os GOARCH=$arch go build \
|
||||
-ldflags "-X main.version=${VERSION} -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
-o "${BUILD_DIR}/${output_name}" \
|
||||
./cmd/hostkeeper
|
||||
|
||||
if command -v shasum &> /dev/null; then
|
||||
(cd ${BUILD_DIR} && shasum -a 256 "${output_name}" > "${output_name}.sha256")
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Build complete! Binaries in ${BUILD_DIR}/"
|
||||
echo ""
|
||||
echo "Available binaries:"
|
||||
ls -lh ${BUILD_DIR}/
|
||||
@@ -0,0 +1,153 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/internal/models"
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/pkg/config"
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/pkg/storage"
|
||||
)
|
||||
|
||||
func TestIntegrationWorkflow(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
store, err := storage.NewJSONStorage(tempDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create storage: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Test 1: Add hosts
|
||||
t.Run("AddHosts", func(t *testing.T) {
|
||||
testHost := &models.Host{
|
||||
ID: "integration-test-1",
|
||||
Name: "Integration Test Server",
|
||||
Hostname: "test.example.com",
|
||||
Port: 22,
|
||||
Username: "testuser",
|
||||
Auth: models.AuthConfig{Type: "password", Password: "testpass"},
|
||||
Tags: []string{"test", "integration"},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := store.SaveHost(ctx, testHost); err != nil {
|
||||
t.Errorf("Failed to save host: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
// Test 2: List hosts
|
||||
t.Run("ListHosts", func(t *testing.T) {
|
||||
hosts, err := store.ListHosts(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list hosts: %v", err)
|
||||
}
|
||||
|
||||
if len(hosts) != 1 {
|
||||
t.Errorf("Expected 1 host, got %d", len(hosts))
|
||||
}
|
||||
})
|
||||
|
||||
// Test 3: Get host
|
||||
t.Run("GetHost", func(t *testing.T) {
|
||||
host, err := store.GetHost(ctx, "integration-test-1")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get host: %v", err)
|
||||
}
|
||||
|
||||
if host.Name != "Integration Test Server" {
|
||||
t.Errorf("Expected name 'Integration Test Server', got '%s'", host.Name)
|
||||
}
|
||||
})
|
||||
|
||||
// Test 4: Update host
|
||||
t.Run("UpdateHost", func(t *testing.T) {
|
||||
host, err := store.GetHost(ctx, "integration-test-1")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get host: %v", err)
|
||||
}
|
||||
|
||||
host.Name = "Updated Test Server"
|
||||
if err := store.SaveHost(ctx, host); err != nil {
|
||||
t.Errorf("Failed to update host: %v", err)
|
||||
}
|
||||
|
||||
updated, err := store.GetHost(ctx, "integration-test-1")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get updated host: %v", err)
|
||||
}
|
||||
|
||||
if updated.Name != "Updated Test Server" {
|
||||
t.Errorf("Update failed: expected 'Updated Test Server', got '%s'", updated.Name)
|
||||
}
|
||||
})
|
||||
|
||||
// Test 5: Export/Import
|
||||
t.Run("ExportImport", func(t *testing.T) {
|
||||
exportData, err := store.ExportData(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to export: %v", err)
|
||||
}
|
||||
|
||||
if exportData == nil {
|
||||
t.Error("Exported data is nil")
|
||||
return
|
||||
}
|
||||
|
||||
if len(exportData.Hosts) != 1 {
|
||||
t.Errorf("Expected 1 host in export, got %d", len(exportData.Hosts))
|
||||
}
|
||||
|
||||
importDir := t.TempDir()
|
||||
importStore, err := storage.NewJSONStorage(importDir)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create import storage: %v", err)
|
||||
}
|
||||
|
||||
if err := importStore.ImportData(ctx, exportData, storage.MergeStrategyReplace); err != nil {
|
||||
t.Errorf("Failed to import: %v", err)
|
||||
}
|
||||
|
||||
importedHosts, err := importStore.ListHosts(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list imported hosts: %v", err)
|
||||
}
|
||||
|
||||
if len(importedHosts) != 1 {
|
||||
t.Errorf("Expected 1 imported host, got %d", len(importedHosts))
|
||||
}
|
||||
})
|
||||
|
||||
// Test 6: Delete host
|
||||
t.Run("DeleteHost", func(t *testing.T) {
|
||||
if err := store.DeleteHost(ctx, "integration-test-1"); err != nil {
|
||||
t.Errorf("Failed to delete host: %v", err)
|
||||
}
|
||||
|
||||
hosts, err := store.ListHosts(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to list hosts after deletion: %v", err)
|
||||
}
|
||||
|
||||
if len(hosts) != 0 {
|
||||
t.Errorf("Expected 0 hosts after deletion, got %d", len(hosts))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigIntegration(t *testing.T) {
|
||||
cfg, err := config.New()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to load/create config: %v", err)
|
||||
}
|
||||
|
||||
if cfg.GetAppConfig().DefaultPort != 22 {
|
||||
t.Errorf("Expected default port 22, got %d", cfg.GetAppConfig().DefaultPort)
|
||||
}
|
||||
|
||||
if cfg.GetAppConfig().ConnectionTimeout != 30 {
|
||||
t.Errorf("Expected default timeout 30, got %d", cfg.GetAppConfig().ConnectionTimeout)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user