go/internal/cmd/php/container_test.go
Snider 03c9188d79
feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs

Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.

Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.

Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add collect, config, crypt, plugin packages and fix all lint issues

Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)

Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.

Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00

383 lines
10 KiB
Go

package php
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDockerBuildOptions_Good(t *testing.T) {
t.Run("all fields accessible", func(t *testing.T) {
opts := DockerBuildOptions{
ProjectDir: "/project",
ImageName: "myapp",
Tag: "v1.0.0",
Platform: "linux/amd64",
Dockerfile: "/path/to/Dockerfile",
NoBuildCache: true,
BuildArgs: map[string]string{"ARG1": "value1"},
Output: os.Stdout,
}
assert.Equal(t, "/project", opts.ProjectDir)
assert.Equal(t, "myapp", opts.ImageName)
assert.Equal(t, "v1.0.0", opts.Tag)
assert.Equal(t, "linux/amd64", opts.Platform)
assert.Equal(t, "/path/to/Dockerfile", opts.Dockerfile)
assert.True(t, opts.NoBuildCache)
assert.Equal(t, "value1", opts.BuildArgs["ARG1"])
assert.NotNil(t, opts.Output)
})
}
func TestLinuxKitBuildOptions_Good(t *testing.T) {
t.Run("all fields accessible", func(t *testing.T) {
opts := LinuxKitBuildOptions{
ProjectDir: "/project",
OutputPath: "/output/image.qcow2",
Format: "qcow2",
Template: "server-php",
Variables: map[string]string{"VAR1": "value1"},
Output: os.Stdout,
}
assert.Equal(t, "/project", opts.ProjectDir)
assert.Equal(t, "/output/image.qcow2", opts.OutputPath)
assert.Equal(t, "qcow2", opts.Format)
assert.Equal(t, "server-php", opts.Template)
assert.Equal(t, "value1", opts.Variables["VAR1"])
assert.NotNil(t, opts.Output)
})
}
func TestServeOptions_Good(t *testing.T) {
t.Run("all fields accessible", func(t *testing.T) {
opts := ServeOptions{
ImageName: "myapp",
Tag: "latest",
ContainerName: "myapp-container",
Port: 8080,
HTTPSPort: 8443,
Detach: true,
EnvFile: "/path/to/.env",
Volumes: map[string]string{"/host": "/container"},
Output: os.Stdout,
}
assert.Equal(t, "myapp", opts.ImageName)
assert.Equal(t, "latest", opts.Tag)
assert.Equal(t, "myapp-container", opts.ContainerName)
assert.Equal(t, 8080, opts.Port)
assert.Equal(t, 8443, opts.HTTPSPort)
assert.True(t, opts.Detach)
assert.Equal(t, "/path/to/.env", opts.EnvFile)
assert.Equal(t, "/container", opts.Volumes["/host"])
assert.NotNil(t, opts.Output)
})
}
func TestIsPHPProject_Container_Good(t *testing.T) {
t.Run("returns true with composer.json", func(t *testing.T) {
dir := t.TempDir()
err := os.WriteFile(filepath.Join(dir, "composer.json"), []byte(`{}`), 0644)
require.NoError(t, err)
assert.True(t, IsPHPProject(dir))
})
}
func TestIsPHPProject_Container_Bad(t *testing.T) {
t.Run("returns false without composer.json", func(t *testing.T) {
dir := t.TempDir()
assert.False(t, IsPHPProject(dir))
})
t.Run("returns false for non-existent directory", func(t *testing.T) {
assert.False(t, IsPHPProject("/non/existent/path"))
})
}
func TestLookupLinuxKit_Bad(t *testing.T) {
t.Run("returns error when linuxkit not found", func(t *testing.T) {
// Save original PATH and paths
origPath := os.Getenv("PATH")
origCommonPaths := commonLinuxKitPaths
defer func() {
_ = os.Setenv("PATH", origPath)
commonLinuxKitPaths = origCommonPaths
}()
// Set PATH to empty and clear common paths
_ = os.Setenv("PATH", "")
commonLinuxKitPaths = []string{}
_, err := lookupLinuxKit()
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "linuxkit not found")
}
})
}
func TestGetLinuxKitTemplate_Good(t *testing.T) {
t.Run("returns server-php template", func(t *testing.T) {
content, err := getLinuxKitTemplate("server-php")
assert.NoError(t, err)
assert.Contains(t, content, "kernel:")
assert.Contains(t, content, "linuxkit/kernel")
})
}
func TestGetLinuxKitTemplate_Bad(t *testing.T) {
t.Run("returns error for unknown template", func(t *testing.T) {
_, err := getLinuxKitTemplate("unknown-template")
assert.Error(t, err)
assert.Contains(t, err.Error(), "template not found")
})
}
func TestApplyTemplateVariables_Good(t *testing.T) {
t.Run("replaces variables", func(t *testing.T) {
content := "Hello ${NAME}, welcome to ${PLACE}!"
vars := map[string]string{
"NAME": "World",
"PLACE": "Earth",
}
result, err := applyTemplateVariables(content, vars)
assert.NoError(t, err)
assert.Equal(t, "Hello World, welcome to Earth!", result)
})
t.Run("handles empty variables", func(t *testing.T) {
content := "No variables here"
vars := map[string]string{}
result, err := applyTemplateVariables(content, vars)
assert.NoError(t, err)
assert.Equal(t, "No variables here", result)
})
t.Run("leaves unmatched placeholders", func(t *testing.T) {
content := "Hello ${NAME}, ${UNKNOWN} is unknown"
vars := map[string]string{
"NAME": "World",
}
result, err := applyTemplateVariables(content, vars)
assert.NoError(t, err)
assert.Contains(t, result, "Hello World")
assert.Contains(t, result, "${UNKNOWN}")
})
t.Run("handles multiple occurrences", func(t *testing.T) {
content := "${VAR} and ${VAR} again"
vars := map[string]string{
"VAR": "value",
}
result, err := applyTemplateVariables(content, vars)
assert.NoError(t, err)
assert.Equal(t, "value and value again", result)
})
}
func TestDefaultServerPHPTemplate_Good(t *testing.T) {
t.Run("template has required sections", func(t *testing.T) {
assert.Contains(t, defaultServerPHPTemplate, "kernel:")
assert.Contains(t, defaultServerPHPTemplate, "init:")
assert.Contains(t, defaultServerPHPTemplate, "services:")
assert.Contains(t, defaultServerPHPTemplate, "onboot:")
})
t.Run("template contains placeholders", func(t *testing.T) {
assert.Contains(t, defaultServerPHPTemplate, "${SSH_KEY:-}")
})
}
func TestBuildDocker_Bad(t *testing.T) {
t.Skip("requires Docker installed")
t.Run("fails for non-PHP project", func(t *testing.T) {
dir := t.TempDir()
err := BuildDocker(context.TODO(), DockerBuildOptions{ProjectDir: dir})
assert.Error(t, err)
assert.Contains(t, err.Error(), "not a PHP project")
})
}
func TestBuildLinuxKit_Bad(t *testing.T) {
t.Skip("requires linuxkit installed")
t.Run("fails for non-PHP project", func(t *testing.T) {
dir := t.TempDir()
err := BuildLinuxKit(context.TODO(), LinuxKitBuildOptions{ProjectDir: dir})
assert.Error(t, err)
assert.Contains(t, err.Error(), "not a PHP project")
})
}
func TestServeProduction_Bad(t *testing.T) {
t.Run("fails without image name", func(t *testing.T) {
err := ServeProduction(context.TODO(), ServeOptions{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "image name is required")
})
}
func TestShell_Bad(t *testing.T) {
t.Run("fails without container ID", func(t *testing.T) {
err := Shell(context.TODO(), "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "container ID is required")
})
}
func TestResolveDockerContainerID_Bad(t *testing.T) {
t.Skip("requires Docker installed")
}
func TestBuildDocker_DefaultOptions(t *testing.T) {
t.Run("sets defaults correctly", func(t *testing.T) {
// This tests the default logic without actually running Docker
opts := DockerBuildOptions{}
// Verify default values would be set in BuildDocker
if opts.Tag == "" {
opts.Tag = "latest"
}
assert.Equal(t, "latest", opts.Tag)
if opts.ImageName == "" {
opts.ImageName = filepath.Base("/project/myapp")
}
assert.Equal(t, "myapp", opts.ImageName)
})
}
func TestBuildLinuxKit_DefaultOptions(t *testing.T) {
t.Run("sets defaults correctly", func(t *testing.T) {
opts := LinuxKitBuildOptions{}
// Verify default values would be set
if opts.Template == "" {
opts.Template = "server-php"
}
assert.Equal(t, "server-php", opts.Template)
if opts.Format == "" {
opts.Format = "qcow2"
}
assert.Equal(t, "qcow2", opts.Format)
})
}
func TestServeProduction_DefaultOptions(t *testing.T) {
t.Run("sets defaults correctly", func(t *testing.T) {
opts := ServeOptions{ImageName: "myapp"}
// Verify default values would be set
if opts.Tag == "" {
opts.Tag = "latest"
}
assert.Equal(t, "latest", opts.Tag)
if opts.Port == 0 {
opts.Port = 80
}
assert.Equal(t, 80, opts.Port)
if opts.HTTPSPort == 0 {
opts.HTTPSPort = 443
}
assert.Equal(t, 443, opts.HTTPSPort)
})
}
func TestLookupLinuxKit_Good(t *testing.T) {
t.Skip("requires linuxkit installed")
t.Run("finds linuxkit in PATH", func(t *testing.T) {
path, err := lookupLinuxKit()
assert.NoError(t, err)
assert.NotEmpty(t, path)
})
}
func TestBuildDocker_WithCustomDockerfile(t *testing.T) {
t.Skip("requires Docker installed")
t.Run("uses custom Dockerfile when provided", func(t *testing.T) {
dir := t.TempDir()
err := os.WriteFile(filepath.Join(dir, "composer.json"), []byte(`{"name":"test"}`), 0644)
require.NoError(t, err)
dockerfilePath := filepath.Join(dir, "Dockerfile.custom")
err = os.WriteFile(dockerfilePath, []byte("FROM alpine"), 0644)
require.NoError(t, err)
opts := DockerBuildOptions{
ProjectDir: dir,
Dockerfile: dockerfilePath,
}
// The function would use the custom Dockerfile
assert.Equal(t, dockerfilePath, opts.Dockerfile)
})
}
func TestBuildDocker_GeneratesDockerfile(t *testing.T) {
t.Skip("requires Docker installed")
t.Run("generates Dockerfile when not provided", func(t *testing.T) {
dir := t.TempDir()
// Create valid PHP project
composerJSON := `{"name":"test","require":{"php":"^8.2","laravel/framework":"^11.0"}}`
err := os.WriteFile(filepath.Join(dir, "composer.json"), []byte(composerJSON), 0644)
require.NoError(t, err)
opts := DockerBuildOptions{
ProjectDir: dir,
// Dockerfile not specified - should be generated
}
assert.Empty(t, opts.Dockerfile)
})
}
func TestServeProduction_BuildsCorrectArgs(t *testing.T) {
t.Run("builds correct docker run arguments", func(t *testing.T) {
opts := ServeOptions{
ImageName: "myapp",
Tag: "v1.0.0",
ContainerName: "myapp-prod",
Port: 8080,
HTTPSPort: 8443,
Detach: true,
EnvFile: "/path/.env",
Volumes: map[string]string{
"/host/storage": "/app/storage",
},
}
// Verify the expected image reference format
imageRef := opts.ImageName + ":" + opts.Tag
assert.Equal(t, "myapp:v1.0.0", imageRef)
// Verify port format
portMapping := opts.Port
assert.Equal(t, 8080, portMapping)
})
}
func TestShell_Integration(t *testing.T) {
t.Skip("requires Docker with running container")
}
func TestResolveDockerContainerID_Integration(t *testing.T) {
t.Skip("requires Docker with running containers")
}