nomadcode/services/core/cmd/plane-smoke/main_test.go

194 lines
5.6 KiB
Go

package main
import (
"context"
"strings"
"testing"
"github.com/nomadcode/nomadcode-core/internal/adapters/plane"
)
type mockPlaneClient struct {
getItemFunc func(ctx context.Context, ref plane.WorkItemRef) (plane.WorkItem, error)
addCommentFunc func(ctx context.Context, input plane.AddCommentInput) error
updateFunc func(ctx context.Context, input plane.UpdateIssueStatusInput) error
}
func (m *mockPlaneClient) GetWorkItem(ctx context.Context, ref plane.WorkItemRef) (plane.WorkItem, error) {
return m.getItemFunc(ctx, ref)
}
func (m *mockPlaneClient) AddComment(ctx context.Context, input plane.AddCommentInput) error {
return m.addCommentFunc(ctx, input)
}
func (m *mockPlaneClient) UpdateIssueStatus(ctx context.Context, input plane.UpdateIssueStatusInput) error {
return m.updateFunc(ctx, input)
}
func TestLoadConfig(t *testing.T) {
tests := []struct {
name string
env map[string]string
wantErr bool
errMsg string
}{
{
name: "valid read-only config",
env: map[string]string{
"PLANE_BASE_URL": "https://plane.example.com",
"PLANE_TOKEN": "secret-token",
"PLANE_WORKSPACE_SLUG": "general",
"PLANE_PROJECT_ID": "proj-1",
"PLANE_WORK_ITEM_ID": "item-1",
},
wantErr: false,
},
{
name: "missing required fields",
env: map[string]string{
"PLANE_BASE_URL": "https://plane.example.com",
},
wantErr: true,
errMsg: "missing required environment variables",
},
{
name: "valid write config",
env: map[string]string{
"PLANE_BASE_URL": "https://plane.example.com",
"PLANE_TOKEN": "secret-token",
"PLANE_WORKSPACE_SLUG": "general",
"PLANE_PROJECT_ID": "proj-1",
"PLANE_WORK_ITEM_ID": "item-1",
"PLANE_SMOKE_APPLY": "1",
"PLANE_SMOKE_COMMENT": "test comment",
"PLANE_SMOKE_STATE_ID": "state-done",
},
wantErr: false,
},
{
name: "missing write fields when apply=1",
env: map[string]string{
"PLANE_BASE_URL": "https://plane.example.com",
"PLANE_TOKEN": "secret-token",
"PLANE_WORKSPACE_SLUG": "general",
"PLANE_PROJECT_ID": "proj-1",
"PLANE_WORK_ITEM_ID": "item-1",
"PLANE_SMOKE_APPLY": "1",
},
wantErr: true,
errMsg: "missing required smoke variables",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
getenv := func(key string) string {
return tt.env[key]
}
cfg, err := LoadConfig(getenv)
if (err != nil) != tt.wantErr {
t.Fatalf("LoadConfig() error = %v, wantErr = %v", err, tt.wantErr)
}
if err != nil && tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
t.Fatalf("expected error containing %q, got %q", tt.errMsg, err.Error())
}
if err == nil {
if cfg.BaseURL != tt.env["PLANE_BASE_URL"] {
t.Errorf("expected BaseURL %q, got %q", tt.env["PLANE_BASE_URL"], cfg.BaseURL)
}
if cfg.Token != tt.env["PLANE_TOKEN"] {
t.Errorf("expected Token %q, got %q", tt.env["PLANE_TOKEN"], cfg.Token)
}
}
})
}
}
func TestRunSmoke(t *testing.T) {
cfg := EnvConfig{
BaseURL: "https://plane.example.com",
Token: "secret-token",
WorkspaceSlug: "general",
ProjectID: "proj-1",
WorkItemID: "item-1",
}
t.Run("read-only mode", func(t *testing.T) {
var builder strings.Builder
client := &mockPlaneClient{
getItemFunc: func(ctx context.Context, ref plane.WorkItemRef) (plane.WorkItem, error) {
return plane.WorkItem{ID: "item-1", Name: "Test Issue"}, nil
},
}
err := RunSmoke(context.Background(), cfg, client, &builder)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := builder.String()
if !strings.Contains(out, "lookup ok: id=item-1 name=Test Issue") {
t.Errorf("unexpected output: %q", out)
}
if !strings.Contains(out, "write smoke skipped") {
t.Errorf("expected write smoke skipped message, got %q", out)
}
})
t.Run("write apply mode", func(t *testing.T) {
writeCfg := cfg
writeCfg.SmokeApply = true
writeCfg.SmokeComment = "test comment"
writeCfg.SmokeStateID = "state-done"
var builder strings.Builder
var commentCalled, stateCalled bool
client := &mockPlaneClient{
getItemFunc: func(ctx context.Context, ref plane.WorkItemRef) (plane.WorkItem, error) {
return plane.WorkItem{ID: "item-1", Name: "Test Issue"}, nil
},
addCommentFunc: func(ctx context.Context, input plane.AddCommentInput) error {
commentCalled = true
if input.CommentHTML != "<p>test comment</p>" {
t.Errorf("unexpected comment body: %q", input.CommentHTML)
}
return nil
},
updateFunc: func(ctx context.Context, input plane.UpdateIssueStatusInput) error {
stateCalled = true
if input.Status != "state-done" {
t.Errorf("unexpected status: %q", input.Status)
}
return nil
},
}
err := RunSmoke(context.Background(), writeCfg, client, &builder)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !commentCalled || !stateCalled {
t.Errorf("expected write methods to be called: comment=%t, state=%t", commentCalled, stateCalled)
}
out := builder.String()
if !strings.Contains(out, "lookup ok") || !strings.Contains(out, "comment ok") || !strings.Contains(out, "state update ok") {
t.Errorf("unexpected output: %q", out)
}
})
}
func TestRedactToken(t *testing.T) {
token := "super-secret-key-123"
errStr := "api error with token super-secret-key-123 failed"
redacted := redactToken(errStr, token)
if strings.Contains(redacted, token) {
t.Errorf("token was not redacted: %q", redacted)
}
if !strings.Contains(redacted, "<redacted>") {
t.Errorf("redacted string should contain <redacted>: %q", redacted)
}
}