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:
swanadiva
2026-06-23 14:03:08 +07:00
parent 30b57f6084
commit c2d6aabea0
4 changed files with 229 additions and 7 deletions
+12 -1
View File
@@ -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
BINARY_NAME=hostkeeper BINARY_NAME=hostkeeper
@@ -37,10 +37,17 @@ test:
test-short: test-short:
$(GOTEST) ./... -v -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: Remove build artifacts
clean: clean:
$(GOCLEAN) $(GOCLEAN)
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)
rm -f coverage.out coverage.html
## fmt: Format all Go files ## fmt: Format all Go files
fmt: fmt:
@@ -66,6 +73,10 @@ tidy:
deps: deps:
$(GOCMD) mod download $(GOCMD) mod download
## verify: Verify module dependencies
verify:
$(GOCMD) mod verify
## release: Build for multiple platforms ## release: Build for multiple platforms
release: clean release: clean
mkdir -p $(BUILD_DIR) mkdir -p $(BUILD_DIR)
+16 -6
View File
@@ -2,7 +2,7 @@
> **Purpose**: Enable seamless continuation of development by any agent/LLM across sessions > **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 > **Current Status**: Implementation In Progress - Tasks 1-8 Complete
> **Phase**: MVP Development (Phase 1) > **Phase**: MVP Development (Phase 1)
@@ -33,7 +33,6 @@
**Bug Fix**: Fixed deadlock in JSON storage (RLock within Lock) **Bug Fix**: Fixed deadlock in JSON storage (RLock within Lock)
### What Needs to Happen Next ### What Needs to Happen Next
🔄 **Task 12**: Build and Testing (Makefile, integration tests)
🔄 **Task 13**: Documentation (README, usage docs) 🔄 **Task 13**: Documentation (README, usage docs)
🔄 **Task 14**: Final testing and release prep 🔄 **Task 14**: Final testing and release prep
@@ -54,10 +53,10 @@
| **CLI Framework** | ✅ 100% | Cobra root, version, completion commands | | **CLI Framework** | ✅ 100% | Cobra root, version, completion commands |
| **CLI Commands** | 🟡 70% | add + list + connect + edit + delete + export + import done | | **CLI Commands** | 🟡 70% | add + list + connect + edit + delete + export + import done |
| **TUI** | 🟡 40% | Basic TUI with host list navigation | | **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 | | **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 - `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 - `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 - **Status**: Not Started
- **Details**: See `docs/plans/2024-06-22-hostkeeper-implementation.md` - **Details**: See `docs/plans/2024-06-22-hostkeeper-implementation.md`
@@ -186,7 +194,7 @@
## 🗺️ Development Roadmap ## 🗺️ Development Roadmap
### Current Week Focus ### Current Week Focus
**Target**: Complete Tasks 10+ (TUI, Export/Import) **Target**: Complete Tasks 12+ (Build, Testing, Docs, Release)
### This Sprint ### This Sprint
- [x] Project setup and dependencies - [x] Project setup and dependencies
@@ -201,6 +209,8 @@
- [x] Edit host command - [x] Edit host command
- [x] Delete host command - [x] Delete host command
- [x] TUI implementation - [x] TUI implementation
- [x] Export/Import commands
- [x] Build system and integration tests
### Next Sprint ### Next Sprint
- [ ] TUI implementation (Bubble Tea) - [ ] TUI implementation (Bubble Tea)
Executable
+48
View File
@@ -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}/
+153
View File
@@ -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)
}
}