Files
cgalo5758 88db730fcc Add dual licensing and SPDX headers
Introduce a commercial license option alongside AGPL-3.0-only, require a
CLA for contributors, and document the terms in COMMERCIAL.md and
NOTICE. Add a script to stamp SPDX headers on Go files and apply it
across the tree.
2026-09-06 02:29:42 -05:00

141 lines
5.0 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
package workflows
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/mock"
"go.temporal.io/sdk/testsuite"
"git.coopcloud.tech/wiki-cafe/member-console/internal/systemtenant"
)
// TestSyncWorkflowResolvesWorkspaceFirst asserts Step 0
// (ResolveSystemWorkspaceActivity) runs before Step 1 (ListFedWikiSitesActivity)
// and that the workspace ID it returns is threaded into SyncSitesToDBActivity's
// input — i.e. the sync projects into the run-time resolved System workspace,
// not a value carried in workflow input.
func TestSyncWorkflowResolvesWorkspaceFirst(t *testing.T) {
var suite testsuite.WorkflowTestSuite
env := suite.NewTestWorkflowEnvironment()
// A nil *Activities is sufficient: OnActivity reflects on the method value
// only to derive the registered activity name; the mock intercepts before
// the (nil) receiver is ever dereferenced — the same idiom the workflow uses.
var acts *Activities
const wantWS = "workspace-system-abc"
var callOrder []string
var syncInputWS string
sawSyncInput := false
env.OnActivity(acts.ResolveSystemWorkspaceActivity, mock.Anything, mock.Anything).
Run(func(mock.Arguments) { callOrder = append(callOrder, "resolve") }).
Return(&ResolveSystemWorkspaceOutput{WorkspaceID: wantWS}, nil)
env.OnActivity(acts.ListFedWikiSitesActivity, mock.Anything, mock.Anything).
Run(func(mock.Arguments) { callOrder = append(callOrder, "list") }).
Return(&ListFedWikiSitesOutput{Sites: []SiteInfo{}}, nil)
env.OnActivity(acts.SyncSitesToDBActivity, mock.Anything, mock.Anything).
Run(func(args mock.Arguments) {
in, ok := args.Get(1).(SyncSitesToDBInput)
if !ok {
t.Fatalf("SyncSitesToDBActivity arg 1 = %T, want SyncSitesToDBInput", args.Get(1))
}
sawSyncInput = true
syncInputWS = in.DefaultWorkspaceID
}).
Return(&SyncSitesToDBOutput{}, nil)
env.OnActivity(acts.ReconcileFedWikiQuotaActivity, mock.Anything, mock.Anything).
Return(&ReconcileFedWikiQuotaOutput{}, nil)
env.OnActivity(acts.PurgeExpiredArchivedActivity, mock.Anything, mock.Anything).
Return(&PurgeExpiredArchivedOutput{}, nil)
env.ExecuteWorkflow(SyncFedWikiSitesWorkflow, SyncFedWikiSitesWorkflowInput{})
if !env.IsWorkflowCompleted() {
t.Fatal("workflow did not complete")
}
if err := env.GetWorkflowError(); err != nil {
t.Fatalf("workflow error: %v", err)
}
var out *SyncFedWikiSitesWorkflowOutput
if err := env.GetWorkflowResult(&out); err != nil {
t.Fatalf("get result: %v", err)
}
if !out.Success {
t.Errorf("Success = false, want true (%q)", out.ErrorMessage)
}
if len(callOrder) < 2 || callOrder[0] != "resolve" || callOrder[1] != "list" {
t.Fatalf("call order = %v, want resolve before list", callOrder)
}
if !sawSyncInput {
t.Fatal("SyncSitesToDBActivity was never called")
}
if syncInputWS != wantWS {
t.Errorf("SyncSitesToDBInput.DefaultWorkspaceID = %q, want %q", syncInputWS, wantWS)
}
}
// TestSyncWorkflowResolveFailureReturnsFriendlyOutput asserts that a Step 0
// failure yields (*SyncFedWikiSitesWorkflowOutput{Success:false}, nil) — the same
// log-and-return shape Step 1 uses — rather than a workflow-level error.
func TestSyncWorkflowResolveFailureReturnsFriendlyOutput(t *testing.T) {
var suite testsuite.WorkflowTestSuite
env := suite.NewTestWorkflowEnvironment()
var acts *Activities
env.OnActivity(acts.ResolveSystemWorkspaceActivity, mock.Anything, mock.Anything).
Return(nil, errors.New("system tenant unavailable"))
env.ExecuteWorkflow(SyncFedWikiSitesWorkflow, SyncFedWikiSitesWorkflowInput{})
if !env.IsWorkflowCompleted() {
t.Fatal("workflow did not complete")
}
if err := env.GetWorkflowError(); err != nil {
t.Fatalf("workflow returned an error, want friendly output: %v", err)
}
var out *SyncFedWikiSitesWorkflowOutput
if err := env.GetWorkflowResult(&out); err != nil {
t.Fatalf("get result: %v", err)
}
if out.Success {
t.Error("Success = true, want false on resolve failure")
}
if out.ErrorMessage == "" {
t.Error("ErrorMessage is empty, want a friendly message")
}
}
// TestResolveSystemWorkspaceActivity_DB checks that the activity resolves the
// same workspace systemtenant.Ensure resolves directly against the same DB.
// DB-gated (TEST_DATABASE_URL), reusing reconcile_db_test.go's env setup.
func TestResolveSystemWorkspaceActivity_DB(t *testing.T) {
acts, database := newReconcileTestEnv(t)
ctx := context.Background()
out, err := acts.ResolveSystemWorkspaceActivity(ctx, ResolveSystemWorkspaceInput{})
if err != nil {
t.Fatalf("ResolveSystemWorkspaceActivity: %v", err)
}
if out.WorkspaceID == "" {
t.Fatal("resolved WorkspaceID is empty")
}
// Ensure is idempotent, so a direct call must yield the identical workspace.
want, err := systemtenant.Ensure(ctx, database)
if err != nil {
t.Fatalf("systemtenant.Ensure: %v", err)
}
if out.WorkspaceID != want {
t.Errorf("activity WorkspaceID = %q, systemtenant.Ensure = %q", out.WorkspaceID, want)
}
}