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(nil, "") 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") }