Feat v0.3.7 package audit (#20)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m36s
CI/CD / test-sqlite (push) Successful in 2m41s
CI/CD / build-and-deploy (push) Successful in 1m13s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #20.
This commit is contained in:
2026-03-28 21:22:39 +00:00
committed by xcaliber
parent d68451fe8e
commit d91ec02dd7
47 changed files with 2067 additions and 53 deletions

View File

@@ -468,3 +468,162 @@ func TestEngine_ErrorCases(t *testing.T) {
t.Fatalf("error = %q, want 'public entry'", err.Error())
}
}
// ── Automated Stage Context (started_by) ────
func TestEngine_AutomatedStageContextIncludesStartedBy(t *testing.T) {
database.RequireTestDB(t)
database.TruncateAll(t)
s := testStores(t)
ctx := context.Background()
userID := database.SeedTestUser(t, "AutoCtx", "autoctx@test.com")
teamID := database.SeedTestTeam(t, "AutoCtx Team", userID)
wf := &models.Workflow{
TeamID: &teamID, Name: "AutoCtx WF", Slug: "autoctx-wf",
EntryMode: "team_only", IsActive: true, CreatedBy: userID,
}
s.Workflows.Create(ctx, wf)
hook := "test-pkg:on_run"
stages := []models.WorkflowStage{
{WorkflowID: wf.ID, Ordinal: 0, Name: "intake", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 1, Name: "auto-stage", StageMode: models.StageModeAutomated, Audience: models.AudienceSystem, StageType: models.StageTypeAutomated, AutoTransition: true, StarlarkHook: &hook},
{WorkflowID: wf.ID, Ordinal: 2, Name: "done", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
}
for i := range stages {
s.Workflows.CreateStage(ctx, &stages[i])
}
snapshot, _ := json.Marshal(stages)
s.Workflows.Publish(ctx, &models.WorkflowVersion{WorkflowID: wf.ID, VersionNumber: 1, Snapshot: snapshot})
// Engine with no runner — automated stage silently skips, instance stays at auto-stage
eng := workflow.NewEngine(s, nil, nil)
inst, err := eng.Start(ctx, wf.ID, json.RawMessage(`{}`), userID)
if err != nil {
t.Fatalf("start: %v", err)
}
// Advance intake → auto-stage
inst, err = eng.Advance(ctx, inst.ID, json.RawMessage(`{"submitted":true}`), userID)
if err != nil {
t.Fatalf("advance to auto: %v", err)
}
// Verify the instance records started_by
got, _ := s.Workflows.GetInstance(ctx, inst.ID)
if got.StartedBy != userID {
t.Fatalf("started_by = %q, want %q", got.StartedBy, userID)
}
}
// ── SLA Seconds in Package Install ──────────
func TestPackageInstall_SLASeconds(t *testing.T) {
database.RequireTestDB(t)
database.TruncateAll(t)
s := testStores(t)
ctx := context.Background()
userID := database.SeedTestUser(t, "SLAPkg", "slapkg@test.com")
teamID := database.SeedTestTeam(t, "SLA Team", userID)
// Simulate workflow package install with sla_seconds
wf := &models.Workflow{
TeamID: &teamID, Name: "SLA WF", Slug: "sla-wf",
EntryMode: "team_only", IsActive: true, CreatedBy: userID,
}
s.Workflows.Create(ctx, wf)
sla := 3600
stage := &models.WorkflowStage{
WorkflowID: wf.ID, Ordinal: 0, Name: "critical-fix",
StageMode: models.StageModeForm, Audience: models.AudienceTeam,
StageType: models.StageTypeSimple, SLASeconds: &sla,
}
if err := s.Workflows.CreateStage(ctx, stage); err != nil {
t.Fatalf("create stage: %v", err)
}
// Read it back
stages, err := s.Workflows.ListStages(ctx, wf.ID)
if err != nil {
t.Fatalf("list stages: %v", err)
}
if len(stages) != 1 {
t.Fatalf("got %d stages, want 1", len(stages))
}
if stages[0].SLASeconds == nil || *stages[0].SLASeconds != 3600 {
t.Fatalf("sla_seconds = %v, want 3600", stages[0].SLASeconds)
}
}
// ── Workflow Package Manifest Install ────────
func TestPackageInstall_ManifestRoundtrip(t *testing.T) {
database.RequireTestDB(t)
database.TruncateAll(t)
s := testStores(t)
ctx := context.Background()
userID := database.SeedTestUser(t, "PkgInst", "pkginst@test.com")
_ = database.SeedTestTeam(t, "PkgInst Team", userID)
// Create a workflow manually matching bug-report-triage structure
wf := &models.Workflow{
Name: "Bug Report Triage", Slug: "bug-report-triage-test",
EntryMode: "public_link", IsActive: true, CreatedBy: userID,
}
if err := s.Workflows.Create(ctx, wf); err != nil {
t.Fatalf("create workflow: %v", err)
}
sla := 3600
branchRules, _ := json.Marshal([]map[string]any{
{"field": "severity", "op": "eq", "value": "critical", "target_stage": "fix-critical"},
{"field": "severity", "op": "neq", "value": "critical", "target_stage": "fix-normal"},
})
stages := []models.WorkflowStage{
{WorkflowID: wf.ID, Ordinal: 0, Name: "submit", StageMode: models.StageModeForm, Audience: models.AudiencePublic, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 1, Name: "classify", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, BranchRules: branchRules},
{WorkflowID: wf.ID, Ordinal: 2, Name: "fix-critical", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple, SLASeconds: &sla},
{WorkflowID: wf.ID, Ordinal: 3, Name: "fix-normal", StageMode: models.StageModeForm, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
{WorkflowID: wf.ID, Ordinal: 4, Name: "verify", StageMode: models.StageModeReview, Audience: models.AudienceTeam, StageType: models.StageTypeSimple},
}
for i := range stages {
if err := s.Workflows.CreateStage(ctx, &stages[i]); err != nil {
t.Fatalf("create stage %d: %v", i, err)
}
}
// Verify all stages
got, err := s.Workflows.ListStages(ctx, wf.ID)
if err != nil {
t.Fatalf("list stages: %v", err)
}
if len(got) != 5 {
t.Fatalf("got %d stages, want 5", len(got))
}
// Verify branch rules on classify
for _, g := range got {
if g.Name == "classify" {
if len(g.BranchRules) == 0 {
t.Fatal("classify missing branch_rules")
}
if !strings.Contains(string(g.BranchRules), "fix-critical") {
t.Fatalf("branch_rules missing fix-critical target: %s", string(g.BranchRules))
}
}
if g.Name == "fix-critical" {
if g.SLASeconds == nil || *g.SLASeconds != 3600 {
t.Fatalf("fix-critical sla_seconds = %v, want 3600", g.SLASeconds)
}
}
if g.Name == "submit" && g.Audience != models.AudiencePublic {
t.Fatalf("submit audience = %q, want public", g.Audience)
}
}
}