This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/packages_update_test.go
Jeffrey Smith ebab0c73ba Fix TestBundledInstall_DefaultAllowlist for empty default set
v0.6.17 emptied defaultBundledPackages, but this test still expected
"notes" to be installed via the curated default set. Update assertions
to expect nothing installed when the allowlist is empty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 16:47:53 +00:00

494 lines
14 KiB
Go

package handlers
import (
"archive/zip"
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"armature/database"
"armature/store"
)
// ── Helpers ────────────────────────────────────
// buildPkgBytes creates a .pkg archive in memory and returns the ZIP bytes.
func buildPkgBytes(t *testing.T, manifest map[string]any) []byte {
t.Helper()
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
data, _ := json.Marshal(manifest)
w, _ := zw.Create("manifest.json")
w.Write(data)
zw.Close()
return buf.Bytes()
}
// seedPackage installs a package directly into the DB for testing.
func seedPackage(t *testing.T, stores store.Stores, manifest map[string]any) {
t.Helper()
ctx := context.Background()
id := manifest["id"].(string)
title := manifest["title"].(string)
source := "extension"
if s, ok := manifest["source"].(string); ok {
source = s
}
if err := stores.Packages.Seed(ctx, id, title, source, manifest); err != nil {
t.Fatal(err)
}
version, _ := manifest["version"].(string)
if version == "" {
version = "0.0.0"
}
pkgType, _ := manifest["type"].(string)
if pkgType == "" {
pkgType = "surface"
}
tier, _ := manifest["tier"].(string)
if tier == "" {
tier = "browser"
}
pkg := &store.PackageRegistration{
Title: title,
Type: pkgType,
Version: version,
Tier: tier,
Enabled: true,
Manifest: manifest,
}
if err := stores.Packages.Update(ctx, id, pkg); err != nil {
t.Fatalf("seedPackage Update failed for %s: %v", id, err)
}
// Verify version was actually stored
check, _ := stores.Packages.Get(ctx, id)
if check != nil && check.Version != version {
t.Fatalf("seedPackage: version not stored correctly for %s: got %q want %q", id, check.Version, version)
}
}
// setupUpdateRouter creates a gin router with the update and export routes.
func setupUpdateRouter(t *testing.T, stores store.Stores) *gin.Engine {
t.Helper()
gin.SetMode(gin.TestMode)
r := gin.New()
handler := NewPackageHandler(stores)
r.POST("/api/v1/admin/packages/:id/update", handler.UpdatePackage)
r.GET("/api/v1/admin/packages/:id/export", handler.ExportPackage)
return r
}
// doUpdate performs a multipart upload to the update endpoint.
func doUpdate(router *gin.Engine, pkgID string, pkgBytes []byte) *httptest.ResponseRecorder {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, _ := writer.CreateFormFile("file", "test.pkg")
part.Write(pkgBytes)
writer.Close()
req := httptest.NewRequest("POST", "/api/v1/admin/packages/"+pkgID+"/update", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
return w
}
// ── Tests ────────────────────────────────────
func TestUpdatePackage_VersionBump(t *testing.T) {
stores := newTestStores(t)
ctx := context.Background()
seedPackage(t, stores, map[string]any{
"id": "test-pkg", "title": "Test", "type": "surface", "version": "1.0.0",
})
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "test-pkg", "title": "Test", "type": "surface", "version": "1.1.0",
})
w := doUpdate(router, "test-pkg", pkg)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
// Verify version updated
updated, _ := stores.Packages.Get(ctx, "test-pkg")
if updated.Version != "1.1.0" {
t.Errorf("version = %q, want %q", updated.Version, "1.1.0")
}
// Check response body
var resp map[string]any
json.Unmarshal(w.Body.Bytes(), &resp)
if resp["version"] != "1.1.0" {
t.Errorf("response version = %v, want 1.1.0", resp["version"])
}
if resp["previous_version"] != "1.0.0" {
t.Errorf("response previous_version = %v, want 1.0.0", resp["previous_version"])
}
}
func TestUpdatePackage_SameVersion_Rejected(t *testing.T) {
stores := newTestStores(t)
seedPackage(t, stores, map[string]any{
"id": "test-pkg", "title": "Test", "type": "surface", "version": "1.0.0",
})
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "test-pkg", "title": "Test", "type": "surface", "version": "1.0.0",
})
w := doUpdate(router, "test-pkg", pkg)
if w.Code != http.StatusConflict {
t.Fatalf("expected 409, got %d: %s", w.Code, w.Body.String())
}
}
func TestUpdatePackage_OlderVersion_Rejected(t *testing.T) {
stores := newTestStores(t)
seedPackage(t, stores, map[string]any{
"id": "test-pkg", "title": "Test", "type": "surface", "version": "2.0.0",
})
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "test-pkg", "title": "Test", "type": "surface", "version": "1.0.0",
})
w := doUpdate(router, "test-pkg", pkg)
if w.Code != http.StatusConflict {
t.Fatalf("expected 409, got %d: %s", w.Code, w.Body.String())
}
}
func TestUpdatePackage_CorePackage_Rejected(t *testing.T) {
stores := newTestStores(t)
ctx := context.Background()
// Seed as core package
stores.Packages.Seed(ctx, "core-pkg", "Core", "core", map[string]any{
"id": "core-pkg", "title": "Core", "type": "surface",
})
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "core-pkg", "title": "Core", "type": "surface", "version": "2.0.0",
})
w := doUpdate(router, "core-pkg", pkg)
if w.Code != http.StatusForbidden {
t.Fatalf("expected 403, got %d: %s", w.Code, w.Body.String())
}
}
func TestUpdatePackage_TypeMismatch_Rejected(t *testing.T) {
stores := newTestStores(t)
seedPackage(t, stores, map[string]any{
"id": "test-pkg", "title": "Test", "type": "surface", "version": "1.0.0",
})
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "test-pkg", "title": "Test", "type": "extension", "version": "2.0.0",
"tools": []any{map[string]any{"name": "test"}},
})
w := doUpdate(router, "test-pkg", pkg)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String())
}
}
func TestUpdatePackage_IDMismatch_Rejected(t *testing.T) {
stores := newTestStores(t)
seedPackage(t, stores, map[string]any{
"id": "test-pkg", "title": "Test", "type": "surface", "version": "1.0.0",
})
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "other-pkg", "title": "Other", "type": "surface", "version": "2.0.0",
})
w := doUpdate(router, "test-pkg", pkg)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String())
}
}
func TestUpdatePackage_NotFound(t *testing.T) {
stores := newTestStores(t)
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "nonexistent", "title": "Test", "type": "surface", "version": "1.0.0",
})
w := doUpdate(router, "nonexistent", pkg)
if w.Code != http.StatusNotFound {
t.Fatalf("expected 404, got %d: %s", w.Code, w.Body.String())
}
}
func TestUpdatePackage_SchemaAddColumn(t *testing.T) {
stores := newTestStores(t)
ctx := context.Background()
// Install v1 with a table that has one column
seedPackage(t, stores, map[string]any{
"id": "schema-pkg", "title": "Schema Test", "type": "surface", "version": "1.0.0",
"db_tables": map[string]any{
"items": map[string]any{
"columns": map[string]any{"name": "text"},
},
},
})
// Actually create the table
tables, _ := ParseDBTables(map[string]any{
"db_tables": map[string]any{
"items": map[string]any{
"columns": map[string]any{"name": "text"},
},
},
})
CreateExtTables(ctx, database.TestDB, database.IsPostgres(), stores, "schema-pkg", tables)
// Insert a row to verify data preservation
physical := extPhysicalTable("schema-pkg", "items")
_, err := database.TestDB.ExecContext(ctx,
fmt.Sprintf("INSERT INTO %s (id, name) VALUES ('row1', 'hello')", physical))
if err != nil {
t.Fatal(err)
}
// Update to v2 with an added column
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "schema-pkg", "title": "Schema Test", "type": "surface", "version": "2.0.0",
"db_tables": map[string]any{
"items": map[string]any{
"columns": map[string]any{"name": "text", "priority": "int"},
},
},
})
w := doUpdate(router, "schema-pkg", pkg)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
// Verify new column exists and old data preserved
var name string
var priority sql.NullInt64
err = database.TestDB.QueryRowContext(ctx,
fmt.Sprintf("SELECT name, priority FROM %s WHERE id = 'row1'", physical)).
Scan(&name, &priority)
if err != nil {
t.Fatal("query failed after schema migration:", err)
}
if name != "hello" {
t.Errorf("name = %q, want %q (data should be preserved)", name, "hello")
}
}
func TestUpdatePackage_SchemaAddTable(t *testing.T) {
stores := newTestStores(t)
ctx := context.Background()
// Install v1 with no tables
seedPackage(t, stores, map[string]any{
"id": "schema-pkg-b", "title": "Schema B", "type": "surface", "version": "1.0.0",
})
// Update to v2 with a new table
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "schema-pkg-b", "title": "Schema B", "type": "surface", "version": "2.0.0",
"db_tables": map[string]any{
"audit": map[string]any{
"columns": map[string]any{"action": "text", "actor": "text"},
},
},
})
w := doUpdate(router, "schema-pkg-b", pkg)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
// Verify table was created
physical := extPhysicalTable("schema-pkg-b", "audit")
_, err := database.TestDB.ExecContext(ctx,
fmt.Sprintf("INSERT INTO %s (id, action, actor) VALUES ('row1', 'test', 'user1')", physical))
if err != nil {
t.Fatal("new table should exist after update:", err)
}
}
func TestUpdatePackage_SettingsMerge(t *testing.T) {
stores := newTestStores(t)
ctx := context.Background()
// Install with initial settings
seedPackage(t, stores, map[string]any{
"id": "settings-pkg", "title": "Settings Test", "type": "surface", "version": "1.0.0",
"settings": []any{
map[string]any{"key": "color", "type": "text", "default": "blue"},
},
})
// Set existing settings value
existing := json.RawMessage(`{"color":"red"}`)
stores.Packages.SetPackageSettings(ctx, "settings-pkg", existing)
// Update with new settings key
router := setupUpdateRouter(t, stores)
pkg := buildPkgBytes(t, map[string]any{
"id": "settings-pkg", "title": "Settings Test", "type": "surface", "version": "2.0.0",
"settings": []any{
map[string]any{"key": "color", "type": "text", "default": "blue"},
map[string]any{"key": "size", "type": "int", "default": float64(12)},
},
})
w := doUpdate(router, "settings-pkg", pkg)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
// Verify settings: color preserved as "red", size added with default 12
raw, _ := stores.Packages.GetPackageSettings(ctx, "settings-pkg")
var settings map[string]any
json.Unmarshal(raw, &settings)
if settings["color"] != "red" {
t.Errorf("color = %v, want %q (existing should be preserved)", settings["color"], "red")
}
if settings["size"] != float64(12) {
t.Errorf("size = %v, want %v (new key should get default)", settings["size"], 12)
}
}
func TestExportPackage_Basic(t *testing.T) {
stores := newTestStores(t)
seedPackage(t, stores, map[string]any{
"id": "export-pkg", "title": "Export Test", "type": "surface", "version": "1.0.0",
})
router := setupUpdateRouter(t, stores)
req := httptest.NewRequest("GET", "/api/v1/admin/packages/export-pkg/export", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
// Verify response is a valid zip with manifest.json
zr, err := zip.NewReader(bytes.NewReader(w.Body.Bytes()), int64(w.Body.Len()))
if err != nil {
t.Fatal("response is not a valid zip:", err)
}
foundManifest := false
for _, f := range zr.File {
if f.Name == "manifest.json" {
foundManifest = true
rc, _ := f.Open()
data, _ := io.ReadAll(rc)
rc.Close()
var m map[string]any
if json.Unmarshal(data, &m) != nil {
t.Fatal("manifest.json is not valid JSON")
}
if m["id"] != "export-pkg" {
t.Errorf("manifest id = %v, want export-pkg", m["id"])
}
}
}
if !foundManifest {
t.Error("zip does not contain manifest.json")
}
}
func TestExportPackage_NotFound(t *testing.T) {
stores := newTestStores(t)
router := setupUpdateRouter(t, stores)
req := httptest.NewRequest("GET", "/api/v1/admin/packages/nonexistent/export", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Fatalf("expected 404, got %d", w.Code)
}
}
// TestBundledInstall_DefaultAllowlist verifies that an empty allowlist
// installs nothing (defaultBundledPackages is empty since v0.6.17).
func TestBundledInstall_DefaultAllowlist(t *testing.T) {
stores := newTestStores(t)
ctx := context.Background()
bundledDir := t.TempDir()
packagesDir := t.TempDir()
// Create packages — neither should be installed with empty defaults
buildTestPkg(t, bundledDir, map[string]any{
"id": "notes", "title": "Notes", "type": "surface", "version": "1.0.0",
})
buildTestPkg(t, bundledDir, map[string]any{
"id": "regex-tester", "title": "Regex Tester", "type": "extension", "version": "1.0.0",
})
// Empty allowlist → empty default set → nothing installed
InstallBundledPackages(bundledDir, packagesDir, "", stores, nil)
pkg, _ := stores.Packages.Get(ctx, "notes")
if pkg != nil {
t.Fatal("notes should NOT be installed (default set is empty)")
}
pkg, _ = stores.Packages.Get(ctx, "regex-tester")
if pkg != nil {
t.Fatal("regex-tester should NOT be installed (default set is empty)")
}
}
// TestBundledInstall_WildcardAllowlist verifies "*" installs everything.
func TestBundledInstall_WildcardAllowlist(t *testing.T) {
stores := newTestStores(t)
ctx := context.Background()
bundledDir := t.TempDir()
packagesDir := t.TempDir()
buildTestPkg(t, bundledDir, map[string]any{
"id": "any-pkg", "title": "Any", "type": "surface", "version": "1.0.0",
})
InstallBundledPackages(bundledDir, packagesDir, "*", stores, nil)
pkg, err := stores.Packages.Get(ctx, "any-pkg")
if err != nil || pkg == nil {
t.Fatal("any-pkg should be installed with * wildcard")
}
}