- Move G07 task artifacts to archive (plan, code review, complete.log) - Refactor main.go to use new webhook router - Update plane webhook handlers with improved error handling - Add config for Plane webhook integration - Update docker-compose for webhook testing - Update plane-dev test documentation
289 lines
9.6 KiB
Go
289 lines
9.6 KiB
Go
package config
|
|
|
|
import "testing"
|
|
|
|
func TestLoadA2AEdgeURLPrefersEdgeURL(t *testing.T) {
|
|
t.Setenv("A2A_EDGE_URL", "http://edge.example/a2a")
|
|
t.Setenv("A2A_AGENT_URL", "http://legacy.example/a2a")
|
|
|
|
cfg := Load()
|
|
|
|
if cfg.A2AEdgeURL != "http://edge.example/a2a" {
|
|
t.Fatalf("A2AEdgeURL: got %q", cfg.A2AEdgeURL)
|
|
}
|
|
if cfg.A2AAgentURL != "http://legacy.example/a2a" {
|
|
t.Fatalf("A2AAgentURL: got %q", cfg.A2AAgentURL)
|
|
}
|
|
}
|
|
|
|
func TestLoadA2AEdgeURLFallsBackToLegacyAgentURL(t *testing.T) {
|
|
t.Setenv("A2A_AGENT_URL", "http://legacy.example/a2a")
|
|
|
|
cfg := Load()
|
|
|
|
if cfg.A2AEdgeURL != "http://legacy.example/a2a" {
|
|
t.Fatalf("A2AEdgeURL fallback: got %q", cfg.A2AEdgeURL)
|
|
}
|
|
if cfg.A2AAgentURL != "http://legacy.example/a2a" {
|
|
t.Fatalf("A2AAgentURL: got %q", cfg.A2AAgentURL)
|
|
}
|
|
}
|
|
|
|
func TestLoadModelAndA2ADefaults(t *testing.T) {
|
|
cfg := Load()
|
|
|
|
if cfg.ModelBaseURL != "http://192.168.0.91:11434" {
|
|
t.Fatalf("ModelBaseURL: got %q", cfg.ModelBaseURL)
|
|
}
|
|
if cfg.ModelAPIKey != "ollama" {
|
|
t.Fatalf("ModelAPIKey: got %q", cfg.ModelAPIKey)
|
|
}
|
|
if cfg.ModelName != "qwen3.6:35b-a3b-bf16" {
|
|
t.Fatalf("ModelName: got %q", cfg.ModelName)
|
|
}
|
|
if cfg.ModelContextSize != 262144 {
|
|
t.Fatalf("ModelContextSize: got %d", cfg.ModelContextSize)
|
|
}
|
|
if cfg.ModelTimeoutSec != 300 {
|
|
t.Fatalf("ModelTimeoutSec: got %d", cfg.ModelTimeoutSec)
|
|
}
|
|
if cfg.A2ATimeoutSec != 300 {
|
|
t.Fatalf("A2ATimeoutSec: got %d", cfg.A2ATimeoutSec)
|
|
}
|
|
}
|
|
|
|
func TestConfigLoadsWorkflowTaskTimeout(t *testing.T) {
|
|
cfg := Load()
|
|
if cfg.WorkflowTaskTimeoutSec != 300 {
|
|
t.Fatalf("WorkflowTaskTimeoutSec default: got %d, want 300", cfg.WorkflowTaskTimeoutSec)
|
|
}
|
|
|
|
t.Setenv("WORKFLOW_TASK_TIMEOUT_SEC", "120")
|
|
cfgOverride := Load()
|
|
if cfgOverride.WorkflowTaskTimeoutSec != 120 {
|
|
t.Fatalf("WorkflowTaskTimeoutSec override: got %d, want 120", cfgOverride.WorkflowTaskTimeoutSec)
|
|
}
|
|
}
|
|
|
|
func TestConfigLoadsProtoSocketDefaultsAndOverrides(t *testing.T) {
|
|
cfg := Load()
|
|
if cfg.ProtoSocketPath != "/proto-socket" {
|
|
t.Fatalf("ProtoSocketPath default: got %q, want %q", cfg.ProtoSocketPath, "/proto-socket")
|
|
}
|
|
if cfg.ProtoSocketHeartbeatIntervalSec != 30 {
|
|
t.Fatalf("ProtoSocketHeartbeatIntervalSec default: got %d, want 30", cfg.ProtoSocketHeartbeatIntervalSec)
|
|
}
|
|
if cfg.ProtoSocketHeartbeatWaitSec != 10 {
|
|
t.Fatalf("ProtoSocketHeartbeatWaitSec default: got %d, want 10", cfg.ProtoSocketHeartbeatWaitSec)
|
|
}
|
|
|
|
t.Setenv("PROTO_SOCKET_PATH", "/custom-path")
|
|
t.Setenv("PROTO_SOCKET_HEARTBEAT_INTERVAL_SEC", "15")
|
|
t.Setenv("PROTO_SOCKET_HEARTBEAT_WAIT_SEC", "5")
|
|
|
|
cfgOverride := Load()
|
|
if cfgOverride.ProtoSocketPath != "/custom-path" {
|
|
t.Fatalf("ProtoSocketPath override: got %q, want %q", cfgOverride.ProtoSocketPath, "/custom-path")
|
|
}
|
|
if cfgOverride.ProtoSocketHeartbeatIntervalSec != 15 {
|
|
t.Fatalf("ProtoSocketHeartbeatIntervalSec override: got %d, want 15", cfgOverride.ProtoSocketHeartbeatIntervalSec)
|
|
}
|
|
if cfgOverride.ProtoSocketHeartbeatWaitSec != 5 {
|
|
t.Fatalf("ProtoSocketHeartbeatWaitSec override: got %d, want 5", cfgOverride.ProtoSocketHeartbeatWaitSec)
|
|
}
|
|
}
|
|
|
|
func TestConfigLoadsJiraEnvVars(t *testing.T) {
|
|
t.Setenv("JIRA_BASE_URL", "https://jira.example.com")
|
|
t.Setenv("JIRA_EMAIL", "user@example.com")
|
|
t.Setenv("JIRA_API_TOKEN", "api-token-123")
|
|
|
|
cfg := Load()
|
|
|
|
if cfg.JiraBaseURL != "https://jira.example.com" {
|
|
t.Fatalf("JiraBaseURL: got %q", cfg.JiraBaseURL)
|
|
}
|
|
if cfg.JiraEmail != "user@example.com" {
|
|
t.Fatalf("JiraEmail: got %q", cfg.JiraEmail)
|
|
}
|
|
if cfg.JiraAPIToken != "api-token-123" {
|
|
t.Fatalf("JiraAPIToken: got %q", cfg.JiraAPIToken)
|
|
}
|
|
}
|
|
|
|
func TestConfigLoadsMattermostEnvVars(t *testing.T) {
|
|
t.Setenv("MATTERMOST_BASE_URL", "https://mattermost.example.com")
|
|
t.Setenv("MATTERMOST_TOKEN", "mattermost-token")
|
|
t.Setenv("MATTERMOST_CHANNEL_ID", "channel-id")
|
|
|
|
cfg := Load()
|
|
|
|
if cfg.MattermostBaseURL != "https://mattermost.example.com" {
|
|
t.Fatalf("MattermostBaseURL: got %q", cfg.MattermostBaseURL)
|
|
}
|
|
if cfg.MattermostToken != "mattermost-token" {
|
|
t.Fatalf("MattermostToken: got %q", cfg.MattermostToken)
|
|
}
|
|
if cfg.MattermostChannelID != "channel-id" {
|
|
t.Fatalf("MattermostChannelID: got %q", cfg.MattermostChannelID)
|
|
}
|
|
}
|
|
|
|
func TestConfigMattermostFieldsAreEmptyByDefault(t *testing.T) {
|
|
cfg := Load()
|
|
|
|
if cfg.MattermostBaseURL != "" {
|
|
t.Fatalf("MattermostBaseURL default: got %q, want empty", cfg.MattermostBaseURL)
|
|
}
|
|
if cfg.MattermostToken != "" {
|
|
t.Fatalf("MattermostToken default: got %q, want empty", cfg.MattermostToken)
|
|
}
|
|
if cfg.MattermostChannelID != "" {
|
|
t.Fatalf("MattermostChannelID default: got %q, want empty", cfg.MattermostChannelID)
|
|
}
|
|
}
|
|
|
|
func TestConfigGitoBranchDefaultsAndDisabled(t *testing.T) {
|
|
cfg := Load()
|
|
|
|
if cfg.GitoProtoSocketURL != "" {
|
|
t.Fatalf("GitoProtoSocketURL default: got %q, want empty", cfg.GitoProtoSocketURL)
|
|
}
|
|
if cfg.GitoRepoID != "" {
|
|
t.Fatalf("GitoRepoID default: got %q, want empty", cfg.GitoRepoID)
|
|
}
|
|
if cfg.GitoBranch != "develop" {
|
|
t.Fatalf("GitoBranch default: got %q, want develop", cfg.GitoBranch)
|
|
}
|
|
if cfg.GitoDevelopRepoPath != "" {
|
|
t.Fatalf("GitoDevelopRepoPath default: got %q, want empty", cfg.GitoDevelopRepoPath)
|
|
}
|
|
if cfg.GitoRemoteName != "origin" {
|
|
t.Fatalf("GitoRemoteName default: got %q, want origin", cfg.GitoRemoteName)
|
|
}
|
|
if cfg.RoadmapCreationTodoStateID != "" {
|
|
t.Fatalf("RoadmapCreationTodoStateID default: got %q, want empty", cfg.RoadmapCreationTodoStateID)
|
|
}
|
|
if cfg.GitoBranchEventsEnabled() {
|
|
t.Fatal("GitoBranchEventsEnabled: want false when endpoint/repo unset")
|
|
}
|
|
}
|
|
|
|
func TestConfigGitoBranchEventsEnabledRequiresAllInputs(t *testing.T) {
|
|
t.Setenv("GITO_PROTO_SOCKET_URL", "ws://gito.example/proto-socket")
|
|
if Load().GitoBranchEventsEnabled() {
|
|
t.Fatal("GitoBranchEventsEnabled: want false when repo id is unset")
|
|
}
|
|
|
|
t.Setenv("GITO_REPO_ID", "nomadcode")
|
|
if Load().GitoBranchEventsEnabled() {
|
|
t.Fatal("GitoBranchEventsEnabled: want false when develop repo path is unset")
|
|
}
|
|
|
|
t.Setenv("GITO_DEVELOP_REPO_PATH", "/srv/develop")
|
|
if Load().GitoBranchEventsEnabled() {
|
|
t.Fatal("GitoBranchEventsEnabled: want false when todo state id is unset")
|
|
}
|
|
|
|
t.Setenv("ROADMAP_CREATION_TODO_STATE_ID", "state-todo")
|
|
t.Setenv("GITO_BRANCH", "release")
|
|
cfg := Load()
|
|
|
|
if cfg.GitoProtoSocketURL != "ws://gito.example/proto-socket" {
|
|
t.Fatalf("GitoProtoSocketURL: got %q", cfg.GitoProtoSocketURL)
|
|
}
|
|
if cfg.GitoRepoID != "nomadcode" {
|
|
t.Fatalf("GitoRepoID: got %q", cfg.GitoRepoID)
|
|
}
|
|
if cfg.GitoBranch != "release" {
|
|
t.Fatalf("GitoBranch override: got %q", cfg.GitoBranch)
|
|
}
|
|
if cfg.GitoDevelopRepoPath != "/srv/develop" {
|
|
t.Fatalf("GitoDevelopRepoPath: got %q", cfg.GitoDevelopRepoPath)
|
|
}
|
|
if cfg.RoadmapCreationTodoStateID != "state-todo" {
|
|
t.Fatalf("RoadmapCreationTodoStateID: got %q", cfg.RoadmapCreationTodoStateID)
|
|
}
|
|
if !cfg.GitoBranchEventsEnabled() {
|
|
t.Fatal("GitoBranchEventsEnabled: want true when all inputs set")
|
|
}
|
|
}
|
|
|
|
func TestConfigRoadmapCreationTodoStateIDFallsBackToPlaneEnv(t *testing.T) {
|
|
t.Setenv("PLANE_TODO_STATE_ID", "plane-state")
|
|
cfg := Load()
|
|
if cfg.RoadmapCreationTodoStateID != "plane-state" {
|
|
t.Fatalf("RoadmapCreationTodoStateID fallback: got %q, want plane-state", cfg.RoadmapCreationTodoStateID)
|
|
}
|
|
|
|
t.Setenv("ROADMAP_CREATION_TODO_STATE_ID", "roadmap-state")
|
|
if Load().RoadmapCreationTodoStateID != "roadmap-state" {
|
|
t.Fatalf("RoadmapCreationTodoStateID: ROADMAP_CREATION_TODO_STATE_ID should win")
|
|
}
|
|
}
|
|
|
|
func TestConfigPlaneWebhookDispatchFieldsAreEmptyByDefault(t *testing.T) {
|
|
cfg := Load()
|
|
|
|
if cfg.PlaneWorkspaceSlug != "" {
|
|
t.Fatalf("PlaneWorkspaceSlug default: got %q, want empty", cfg.PlaneWorkspaceSlug)
|
|
}
|
|
if cfg.PlaneWorkspaceID != "" {
|
|
t.Fatalf("PlaneWorkspaceID default: got %q, want empty", cfg.PlaneWorkspaceID)
|
|
}
|
|
if cfg.PlaneCreationBacklogStateID != "" {
|
|
t.Fatalf("PlaneCreationBacklogStateID default: got %q, want empty", cfg.PlaneCreationBacklogStateID)
|
|
}
|
|
if cfg.PlaneAgentAssigneeID != "" {
|
|
t.Fatalf("PlaneAgentAssigneeID default: got %q, want empty", cfg.PlaneAgentAssigneeID)
|
|
}
|
|
if cfg.PlaneSelfActorID != "" {
|
|
t.Fatalf("PlaneSelfActorID default: got %q, want empty", cfg.PlaneSelfActorID)
|
|
}
|
|
}
|
|
|
|
func TestConfigLoadsPlaneWebhookDispatchEnv(t *testing.T) {
|
|
t.Setenv("PLANE_WORKSPACE_SLUG", "general")
|
|
t.Setenv("PLANE_WORKSPACE_ID", "dadf050e-cd1e-4590-bc33-672511630841")
|
|
t.Setenv("PLANE_BACKLOG_STATE_ID", "backlog-state")
|
|
t.Setenv("PLANE_AGENT_ASSIGNEE_ID", "agent-assignee")
|
|
t.Setenv("PLANE_SELF_ACTOR_ID", "self-actor")
|
|
|
|
cfg := Load()
|
|
|
|
if cfg.PlaneWorkspaceSlug != "general" {
|
|
t.Fatalf("PlaneWorkspaceSlug: got %q", cfg.PlaneWorkspaceSlug)
|
|
}
|
|
if cfg.PlaneWorkspaceID != "dadf050e-cd1e-4590-bc33-672511630841" {
|
|
t.Fatalf("PlaneWorkspaceID: got %q", cfg.PlaneWorkspaceID)
|
|
}
|
|
if cfg.PlaneCreationBacklogStateID != "backlog-state" {
|
|
t.Fatalf("PlaneCreationBacklogStateID from PLANE_BACKLOG_STATE_ID: got %q", cfg.PlaneCreationBacklogStateID)
|
|
}
|
|
if cfg.PlaneAgentAssigneeID != "agent-assignee" {
|
|
t.Fatalf("PlaneAgentAssigneeID: got %q", cfg.PlaneAgentAssigneeID)
|
|
}
|
|
if cfg.PlaneSelfActorID != "self-actor" {
|
|
t.Fatalf("PlaneSelfActorID: got %q", cfg.PlaneSelfActorID)
|
|
}
|
|
|
|
// PLANE_CREATION_BACKLOG_STATE_ID takes precedence over the PLANE_BACKLOG_STATE_ID alias.
|
|
t.Setenv("PLANE_CREATION_BACKLOG_STATE_ID", "creation-backlog-state")
|
|
if got := Load().PlaneCreationBacklogStateID; got != "creation-backlog-state" {
|
|
t.Fatalf("PlaneCreationBacklogStateID: PLANE_CREATION_BACKLOG_STATE_ID should win, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestConfigJiraFieldsAreEmptyByDefault(t *testing.T) {
|
|
cfg := Load()
|
|
|
|
if cfg.JiraBaseURL != "" {
|
|
t.Fatalf("JiraBaseURL default: got %q, want empty", cfg.JiraBaseURL)
|
|
}
|
|
if cfg.JiraEmail != "" {
|
|
t.Fatalf("JiraEmail default: got %q, want empty", cfg.JiraEmail)
|
|
}
|
|
if cfg.JiraAPIToken != "" {
|
|
t.Fatalf("JiraAPIToken default: got %q, want empty", cfg.JiraAPIToken)
|
|
}
|
|
}
|