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.
141 lines
5.0 KiB
Go
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)
|
|
}
|
|
}
|