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
|
||||||
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
@@ -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)
|
||||||
|
|||||||
@@ -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