194 lines
5.6 KiB
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)
|
|
}
|
|
}
|