diff --git a/internal/models/application.go b/internal/models/application.go index 208003d..a4303b6 100644 --- a/internal/models/application.go +++ b/internal/models/application.go @@ -1,16 +1,116 @@ package models +// ApplicationSettings represents the settings for a Coolify application +type ApplicationSettings struct { + ID int `json:"-" table:"-"` + ApplicationID int `json:"-" table:"-"` + IsStatic *bool `json:"is_static,omitempty"` + IsBuildServerEnabled *bool `json:"is_build_server_enabled,omitempty"` + IsPreserveRepositoryEnabled *bool `json:"is_preserve_repository_enabled,omitempty"` + IsAutoDeployEnabled *bool `json:"is_auto_deploy_enabled,omitempty"` + IsForceHTTPSEnabled *bool `json:"is_force_https_enabled,omitempty"` + IsDebugEnabled *bool `json:"is_debug_enabled,omitempty"` + IsPreviewDeploymentsEnabled *bool `json:"is_preview_deployments_enabled,omitempty"` + IsGitSubmodulesEnabled *bool `json:"is_git_submodules_enabled,omitempty"` + IsGitLFSEnabled *bool `json:"is_git_lfs_enabled,omitempty"` + CreatedAt string `json:"-" table:"-"` + UpdatedAt string `json:"-" table:"-"` +} + // Application represents a Coolify application type Application struct { - ID int `json:"-" table:"-"` - UUID string `json:"uuid"` - Name string `json:"name"` - Description *string `json:"description,omitempty"` - Status string `json:"status"` - GitBranch *string `json:"git_branch,omitempty"` - FQDN *string `json:"fqdn,omitempty"` - CreatedAt string `json:"-" table:"-"` - UpdatedAt string `json:"-" table:"-"` + // Table-visible fields + UUID string `json:"uuid"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` + Status string `json:"status"` + FQDN *string `json:"fqdn,omitempty"` + GitRepository *string `json:"git_repository,omitempty"` + GitBranch *string `json:"git_branch,omitempty"` + BuildPack *string `json:"build_pack,omitempty"` + PortsExposes *string `json:"ports_exposes,omitempty"` + + // Git extended (JSON-only) + GitCommitSHA *string `json:"git_commit_sha,omitempty" table:"-"` + GitFullURL *string `json:"git_full_url,omitempty" table:"-"` + + // Build configuration (JSON-only) + InstallCommand *string `json:"install_command,omitempty" table:"-"` + BuildCommand *string `json:"build_command,omitempty" table:"-"` + StartCommand *string `json:"start_command,omitempty" table:"-"` + BaseDirectory *string `json:"base_directory,omitempty" table:"-"` + PublishDirectory *string `json:"publish_directory,omitempty" table:"-"` + StaticImage *string `json:"static_image,omitempty" table:"-"` + + // Docker configuration (JSON-only) + Dockerfile *string `json:"dockerfile,omitempty" table:"-"` + DockerfileLocation *string `json:"dockerfile_location,omitempty" table:"-"` + DockerRegistryImageName *string `json:"docker_registry_image_name,omitempty" table:"-"` + DockerRegistryImageTag *string `json:"docker_registry_image_tag,omitempty" table:"-"` + DockerCompose *string `json:"docker_compose,omitempty" table:"-"` + DockerComposeRaw *string `json:"docker_compose_raw,omitempty" table:"-"` + DockerComposeLocation *string `json:"docker_compose_location,omitempty" table:"-"` + CustomDockerRunOptions *string `json:"custom_docker_run_options,omitempty" table:"-"` + CustomLabels *string `json:"custom_labels,omitempty" table:"-"` + CustomNginxConfiguration *string `json:"custom_nginx_configuration,omitempty" table:"-"` + + // Networking (JSON-only) + PortsMappings *string `json:"ports_mappings,omitempty" table:"-"` + Domains *string `json:"domains,omitempty" table:"-"` + Redirect *string `json:"redirect,omitempty" table:"-"` + PreviewURLTemplate *string `json:"preview_url_template,omitempty" table:"-"` + + // Health checks (JSON-only) + HealthCheckEnabled *bool `json:"health_check_enabled,omitempty" table:"-"` + HealthCheckPath *string `json:"health_check_path,omitempty" table:"-"` + HealthCheckPort *string `json:"health_check_port,omitempty" table:"-"` + HealthCheckHost *string `json:"health_check_host,omitempty" table:"-"` + HealthCheckMethod *string `json:"health_check_method,omitempty" table:"-"` + HealthCheckScheme *string `json:"health_check_scheme,omitempty" table:"-"` + HealthCheckReturnCode *int `json:"health_check_return_code,omitempty" table:"-"` + HealthCheckResponseText *string `json:"health_check_response_text,omitempty" table:"-"` + HealthCheckInterval *int `json:"health_check_interval,omitempty" table:"-"` + HealthCheckTimeout *int `json:"health_check_timeout,omitempty" table:"-"` + HealthCheckRetries *int `json:"health_check_retries,omitempty" table:"-"` + HealthCheckStartPeriod *int `json:"health_check_start_period,omitempty" table:"-"` + + // Resource limits (JSON-only) + LimitsCPUs *string `json:"limits_cpus,omitempty" table:"-"` + LimitsCPUShares *int `json:"limits_cpu_shares,omitempty" table:"-"` + LimitsCPUSet *string `json:"limits_cpuset,omitempty" table:"-"` + LimitsMemory *string `json:"limits_memory,omitempty" table:"-"` + LimitsMemoryReservation *string `json:"limits_memory_reservation,omitempty" table:"-"` + LimitsMemorySwap *string `json:"limits_memory_swap,omitempty" table:"-"` + LimitsMemorySwappiness *int `json:"limits_memory_swappiness,omitempty" table:"-"` + + // Deployment hooks (JSON-only) + PreDeploymentCommand *string `json:"pre_deployment_command,omitempty" table:"-"` + PreDeploymentCommandContainer *string `json:"pre_deployment_command_container,omitempty" table:"-"` + PostDeploymentCommand *string `json:"post_deployment_command,omitempty" table:"-"` + PostDeploymentCommandContainer *string `json:"post_deployment_command_container,omitempty" table:"-"` + + // Webhook secrets (JSON-only) + ManualWebhookSecretGitHub *string `json:"manual_webhook_secret_github,omitempty" table:"-" sensitive:"true"` + ManualWebhookSecretGitLab *string `json:"manual_webhook_secret_gitlab,omitempty" table:"-" sensitive:"true"` + ManualWebhookSecretBitbucket *string `json:"manual_webhook_secret_bitbucket,omitempty" table:"-" sensitive:"true"` + ManualWebhookSecretGitea *string `json:"manual_webhook_secret_gitea,omitempty" table:"-" sensitive:"true"` + + // Misc (JSON-only) + WatchPaths *string `json:"watch_paths,omitempty" table:"-"` + SwarmReplicas *int `json:"swarm_replicas,omitempty" table:"-"` + ConfigHash *string `json:"config_hash,omitempty" table:"-"` + + // Nested settings (JSON-only) + Settings *ApplicationSettings `json:"settings,omitempty" table:"-"` + + // Hidden fields (not in JSON or table output) + ID int `json:"-" table:"-"` + EnvironmentID *int `json:"-" table:"-"` + DestinationID *int `json:"-" table:"-"` + SourceID *int `json:"-" table:"-"` + PrivateKeyID *int `json:"-" table:"-"` + CreatedAt string `json:"-" table:"-"` + UpdatedAt string `json:"-" table:"-"` } // ApplicationListItem represents a simplified application for list view diff --git a/internal/models/models_test.go b/internal/models/models_test.go index 86c2685..28c6f60 100644 --- a/internal/models/models_test.go +++ b/internal/models/models_test.go @@ -99,6 +99,126 @@ func TestProject_UnmarshalFromFixture(t *testing.T) { assert.Equal(t, "running", project.Environments[0].Applications[0].Status) } +func TestApplication_MarshalUnmarshal(t *testing.T) { + desc := "Test application" + repo := "https://github.com/example/app" + branch := "main" + fqdn := "https://app.example.com" + buildPack := "nixpacks" + ports := "3000" + installCmd := "npm install" + buildCmd := "npm run build" + startCmd := "npm start" + limitsCPUs := "2" + limitsMemory := "512M" + healthPath := "/health" + isAutoDeployEnabled := true + + app := Application{ + ID: 1, + UUID: "app-uuid", + Name: "My App", + Description: &desc, + Status: "running", + FQDN: &fqdn, + GitRepository: &repo, + GitBranch: &branch, + BuildPack: &buildPack, + PortsExposes: &ports, + InstallCommand: &installCmd, + BuildCommand: &buildCmd, + StartCommand: &startCmd, + LimitsCPUs: &limitsCPUs, + LimitsMemory: &limitsMemory, + HealthCheckPath: &healthPath, + Settings: &ApplicationSettings{ + IsAutoDeployEnabled: &isAutoDeployEnabled, + }, + CreatedAt: "2024-01-01T00:00:00Z", + UpdatedAt: "2024-01-02T00:00:00Z", + } + + // Marshal + data, err := json.Marshal(app) + require.NoError(t, err) + + // Unmarshal + var unmarshaled Application + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + + assert.Equal(t, app.UUID, unmarshaled.UUID) + assert.Equal(t, app.Name, unmarshaled.Name) + assert.Equal(t, *app.GitRepository, *unmarshaled.GitRepository) + assert.Equal(t, *app.BuildPack, *unmarshaled.BuildPack) + assert.Equal(t, *app.InstallCommand, *unmarshaled.InstallCommand) + assert.Equal(t, *app.LimitsCPUs, *unmarshaled.LimitsCPUs) + assert.NotNil(t, unmarshaled.Settings) + assert.True(t, *unmarshaled.Settings.IsAutoDeployEnabled) +} + +func TestApplication_UnmarshalFromFixture(t *testing.T) { + fixtureData, err := os.ReadFile(filepath.Join("..", "..", "test", "fixtures", "application.json")) + require.NoError(t, err) + + var app Application + err = json.Unmarshal(fixtureData, &app) + require.NoError(t, err) + + assert.Equal(t, "app-fixture-uuid-123", app.UUID) + assert.Equal(t, "My Web Application", app.Name) + assert.Equal(t, "running", app.Status) + assert.Equal(t, "https://app.example.com", *app.FQDN) + assert.Equal(t, "https://github.com/example/app", *app.GitRepository) + assert.Equal(t, "main", *app.GitBranch) + assert.Equal(t, "nixpacks", *app.BuildPack) + assert.Equal(t, "3000", *app.PortsExposes) + assert.Equal(t, "npm install", *app.InstallCommand) + assert.Equal(t, "npm run build", *app.BuildCommand) + assert.Equal(t, "npm start", *app.StartCommand) + assert.Equal(t, "/health", *app.HealthCheckPath) + assert.Equal(t, 200, *app.HealthCheckReturnCode) + assert.Equal(t, "2", *app.LimitsCPUs) + assert.Equal(t, "512M", *app.LimitsMemory) + assert.Equal(t, "npm run migrate", *app.PreDeploymentCommand) + assert.Equal(t, 1, *app.SwarmReplicas) + assert.Equal(t, "abc123", *app.ConfigHash) + + // Nested settings + require.NotNil(t, app.Settings) + assert.NotNil(t, app.Settings.IsAutoDeployEnabled) + assert.True(t, *app.Settings.IsAutoDeployEnabled) + assert.True(t, *app.Settings.IsForceHTTPSEnabled) + assert.False(t, *app.Settings.IsStatic) +} + +func TestApplication_JSONExcludesHiddenFields(t *testing.T) { + app := Application{ + ID: 42, + UUID: "app-uuid", + Name: "Test App", + Status: "running", + EnvironmentID: intPtr(1), + DestinationID: intPtr(2), + CreatedAt: "2024-01-01T00:00:00Z", + UpdatedAt: "2024-01-02T00:00:00Z", + } + + data, err := json.Marshal(app) + require.NoError(t, err) + + jsonStr := string(data) + assert.NotContains(t, jsonStr, `"id"`) + assert.NotContains(t, jsonStr, `"environment_id"`) + assert.NotContains(t, jsonStr, `"destination_id"`) + assert.NotContains(t, jsonStr, `"created_at"`) + assert.NotContains(t, jsonStr, `"updated_at"`) + assert.Contains(t, jsonStr, `"uuid"`) + assert.Contains(t, jsonStr, `"name"`) +} + +func intPtr(v int) *int { return &v } + func TestResource_MarshalUnmarshal(t *testing.T) { resource := Resource{ ID: 1, diff --git a/internal/service/application_test.go b/internal/service/application_test.go index e560197..2584aab 100644 --- a/internal/service/application_test.go +++ b/internal/service/application_test.go @@ -110,17 +110,41 @@ func TestApplicationService_Get(t *testing.T) { desc := "Test Application" branch := "main" fqdn := "test.example.com" + repo := "https://github.com/example/app" + buildPack := "nixpacks" + ports := "3000" + installCmd := "npm install" + buildCmd := "npm run build" + startCmd := "npm start" + healthPath := "/health" + limitsCPUs := "2" + limitsMemory := "512M" + preDeployCmd := "npm run migrate" + isAutoDeployEnabled := true application := models.Application{ - ID: 1, - UUID: "app-uuid-123", - Name: "Test App", - Description: &desc, - Status: "running", - GitBranch: &branch, - FQDN: &fqdn, - CreatedAt: "2024-01-01T00:00:00Z", - UpdatedAt: "2024-01-02T00:00:00Z", + ID: 1, + UUID: "app-uuid-123", + Name: "Test App", + Description: &desc, + Status: "running", + GitBranch: &branch, + FQDN: &fqdn, + GitRepository: &repo, + BuildPack: &buildPack, + PortsExposes: &ports, + InstallCommand: &installCmd, + BuildCommand: &buildCmd, + StartCommand: &startCmd, + HealthCheckPath: &healthPath, + LimitsCPUs: &limitsCPUs, + LimitsMemory: &limitsMemory, + PreDeploymentCommand: &preDeployCmd, + Settings: &models.ApplicationSettings{ + IsAutoDeployEnabled: &isAutoDeployEnabled, + }, + CreatedAt: "2024-01-01T00:00:00Z", + UpdatedAt: "2024-01-02T00:00:00Z", } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -144,6 +168,18 @@ func TestApplicationService_Get(t *testing.T) { assert.Equal(t, "running", result.Status) assert.Equal(t, "main", *result.GitBranch) assert.Equal(t, "test.example.com", *result.FQDN) + assert.Equal(t, "https://github.com/example/app", *result.GitRepository) + assert.Equal(t, "nixpacks", *result.BuildPack) + assert.Equal(t, "3000", *result.PortsExposes) + assert.Equal(t, "npm install", *result.InstallCommand) + assert.Equal(t, "npm run build", *result.BuildCommand) + assert.Equal(t, "npm start", *result.StartCommand) + assert.Equal(t, "/health", *result.HealthCheckPath) + assert.Equal(t, "2", *result.LimitsCPUs) + assert.Equal(t, "512M", *result.LimitsMemory) + assert.Equal(t, "npm run migrate", *result.PreDeploymentCommand) + require.NotNil(t, result.Settings) + assert.True(t, *result.Settings.IsAutoDeployEnabled) } func TestApplicationService_Get_NotFound(t *testing.T) { diff --git a/test/fixtures/application.json b/test/fixtures/application.json new file mode 100644 index 0000000..68112a7 --- /dev/null +++ b/test/fixtures/application.json @@ -0,0 +1,76 @@ +{ + "id": 1, + "uuid": "app-fixture-uuid-123", + "name": "My Web Application", + "description": "A comprehensive test fixture", + "status": "running", + "fqdn": "https://app.example.com", + "git_repository": "https://github.com/example/app", + "git_branch": "main", + "git_commit_sha": "abc123def456", + "git_full_url": "https://github.com/example/app.git", + "build_pack": "nixpacks", + "ports_exposes": "3000", + "ports_mappings": "3000:3000", + "domains": "app.example.com", + "redirect": "www", + "install_command": "npm install", + "build_command": "npm run build", + "start_command": "npm start", + "base_directory": "/", + "publish_directory": "/dist", + "dockerfile": null, + "dockerfile_location": "/Dockerfile", + "docker_registry_image_name": null, + "docker_registry_image_tag": null, + "custom_docker_run_options": null, + "custom_labels": null, + "custom_nginx_configuration": null, + "health_check_enabled": true, + "health_check_path": "/health", + "health_check_port": "3000", + "health_check_host": null, + "health_check_method": "GET", + "health_check_scheme": "http", + "health_check_return_code": 200, + "health_check_response_text": null, + "health_check_interval": 30, + "health_check_timeout": 5, + "health_check_retries": 3, + "health_check_start_period": 10, + "limits_cpus": "2", + "limits_memory": "512M", + "limits_memory_swap": "1G", + "limits_memory_swappiness": 60, + "limits_memory_reservation": "256M", + "limits_cpuset": null, + "limits_cpu_shares": 1024, + "pre_deployment_command": "npm run migrate", + "pre_deployment_command_container": null, + "post_deployment_command": null, + "post_deployment_command_container": null, + "watch_paths": null, + "swarm_replicas": 1, + "config_hash": "abc123", + "environment_id": 1, + "destination_id": 2, + "source_id": null, + "private_key_id": null, + "settings": { + "id": 1, + "application_id": 1, + "is_static": false, + "is_build_server_enabled": false, + "is_preserve_repository_enabled": false, + "is_auto_deploy_enabled": true, + "is_force_https_enabled": true, + "is_debug_enabled": false, + "is_preview_deployments_enabled": true, + "is_git_submodules_enabled": false, + "is_git_lfs_enabled": false, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-02T00:00:00Z" + }, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-02T00:00:00Z" +}