Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Integration Tests

on:
workflow_dispatch:
inputs:
scope:
description: 'Test scope to run'
required: true
default: 'all'
type: choice
options:
- all
- container
- function
- datasync
- store

jobs:
integration-test:
name: Integration Tests (${{ inputs.scope }})
runs-on: [self-hosted, local, macOS, ARM64]
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Check Container Engine
run: |
# Detect container engine and export env vars for testcontainers
if [ -n "$DOCKER_HOST" ]; then
echo "Using existing DOCKER_HOST=$DOCKER_HOST"
elif command -v docker &>/dev/null && docker info &>/dev/null 2>&1; then
echo "Using native Docker"
else
SOCKET=$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}' 2>/dev/null || true)
if [ -n "$SOCKET" ] && [ -e "$SOCKET" ]; then
echo "DOCKER_HOST=unix://$SOCKET" >> "$GITHUB_ENV"
echo "TESTCONTAINERS_RYUK_DISABLED=true" >> "$GITHUB_ENV"
echo "Using Podman socket: $SOCKET"
else
echo "::error::No container engine found"
exit 1
fi
fi

- name: Resolve Test Packages
id: packages
run: |
case "${{ inputs.scope }}" in
all) echo "packages=./..." >> "$GITHUB_OUTPUT" ;;
container) echo "packages=./container/..." >> "$GITHUB_OUTPUT" ;;
function) echo "packages=./function/..." >> "$GITHUB_OUTPUT" ;;
datasync) echo "packages=./datasync/..." >> "$GITHUB_OUTPUT" ;;
store) echo "packages=./workflow/store/... ./workflow/artifacts/..." >> "$GITHUB_OUTPUT" ;;
esac

- name: Run Integration Tests
run: |
nix develop --command go test \
-race -count=1 \
-tags=integration \
-timeout=15m \
${{ steps.packages.outputs.packages }}
98 changes: 35 additions & 63 deletions container/activity/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/jasoet/go-wf/container/payload"
)

Expand Down Expand Up @@ -120,11 +122,25 @@ func TestBuildWaitStrategy(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
strategy := buildWaitStrategy(tt.config)
if strategy == nil {
t.Error("buildWaitStrategy returned nil")
assert.NotNil(t, strategy, "buildWaitStrategy(%q) returned nil", tt.config.Type)
})
}
}

func TestBuildWaitStrategy_AllTypeStrings(t *testing.T) {
// Verify all documented type strings produce non-nil strategies
// and that the function handles each without panicking.
types := []string{"log", "port", "http", "healthy", "unknown", ""}
for _, typ := range types {
t.Run("type_"+typ, func(t *testing.T) {
config := payload.WaitStrategyConfig{
Type: typ,
LogMessage: "ready",
Port: "8080",
HTTPPath: "/health",
}
// Successfully built strategy - we can't inspect the internal type easily
// but we verify it doesn't panic and returns non-nil
strategy := buildWaitStrategy(config)
assert.NotNil(t, strategy, "buildWaitStrategy(%q) returned nil", typ)
})
}
}
Expand Down Expand Up @@ -175,65 +191,6 @@ func TestContainerExecutionInput_AllFields(t *testing.T) {
}
}

func TestContainerExecutionOutput_Fields(t *testing.T) {
// Test output structure
startedAt := time.Now()
finishedAt := startedAt.Add(5 * time.Second)
output := payload.ContainerExecutionOutput{
ContainerID: "abc123",
Name: "test",
ExitCode: 0,
Stdout: "output",
Stderr: "errors",
Endpoint: "localhost:8080",
Ports: map[string]string{
"8080": "32768",
},
StartedAt: startedAt,
FinishedAt: finishedAt,
Duration: 5 * time.Second,
Success: true,
Error: "",
}

if output.ContainerID != "abc123" {
t.Errorf("Expected ContainerID abc123, got %s", output.ContainerID)
}
if output.Name != "test" {
t.Errorf("Expected Name test, got %s", output.Name)
}
if output.ExitCode != 0 {
t.Errorf("Expected ExitCode 0, got %d", output.ExitCode)
}
if output.Stdout != "output" {
t.Errorf("Expected Stdout output, got %s", output.Stdout)
}
if output.Stderr != "errors" {
t.Errorf("Expected Stderr errors, got %s", output.Stderr)
}
if output.Endpoint != "localhost:8080" {
t.Errorf("Expected Endpoint localhost:8080, got %s", output.Endpoint)
}
if len(output.Ports) != 1 || output.Ports["8080"] != "32768" {
t.Errorf("Expected Ports map with 8080:32768, got %v", output.Ports)
}
if !output.Success {
t.Error("Expected Success to be true")
}
if output.Error != "" {
t.Errorf("Expected empty Error, got %s", output.Error)
}
if output.Duration != 5*time.Second {
t.Errorf("Expected Duration 5s, got %v", output.Duration)
}
if !output.StartedAt.Equal(startedAt) {
t.Errorf("Expected StartedAt %v, got %v", startedAt, output.StartedAt)
}
if !output.FinishedAt.Equal(finishedAt) {
t.Errorf("Expected FinishedAt %v, got %v", finishedAt, output.FinishedAt)
}
}

func TestPipelineInput_MultipleContainers(t *testing.T) {
input := payload.PipelineInput{
Containers: []payload.ContainerExecutionInput{
Expand Down Expand Up @@ -339,6 +296,21 @@ func TestParallelInput_Concurrency(t *testing.T) {
}
}

func TestParallelInput_NegativeConcurrency(t *testing.T) {
// MaxConcurrency is not currently enforced (see ParallelInput docs).
// Negative values pass validation. This test documents current behavior.
input := payload.ParallelInput{
Containers: []payload.ContainerExecutionInput{
{Image: "alpine:latest"},
},
MaxConcurrency: -1,
FailureStrategy: "continue",
}

err := input.Validate()
assert.NoError(t, err, "Negative MaxConcurrency should pass validation (field is not enforced)")
}

func TestWaitStrategyConfig_AllTypes(t *testing.T) {
configs := []payload.WaitStrategyConfig{
{
Expand Down
40 changes: 0 additions & 40 deletions container/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import (
"fmt"
"testing"

"github.com/nexus-rpc/sdk-go/nexus"
sdkactivity "go.temporal.io/sdk/activity"
sdkworkflow "go.temporal.io/sdk/workflow"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
Expand All @@ -19,26 +15,6 @@ import (
"github.com/jasoet/go-wf/container/workflow"
)

// integrationMockWorker implements the Worker interface for testing.
type integrationMockWorker struct{}

func (m *integrationMockWorker) RegisterWorkflow(interface{}) {}
func (m *integrationMockWorker) RegisterWorkflowWithOptions(interface{}, sdkworkflow.RegisterOptions) {
}

func (m *integrationMockWorker) RegisterDynamicWorkflow(interface{}, sdkworkflow.DynamicRegisterOptions) {
}
func (m *integrationMockWorker) RegisterActivity(interface{}) {}
func (m *integrationMockWorker) RegisterActivityWithOptions(interface{}, sdkactivity.RegisterOptions) {
}

func (m *integrationMockWorker) RegisterDynamicActivity(interface{}, sdkactivity.DynamicRegisterOptions) {
}
func (m *integrationMockWorker) RegisterNexusService(*nexus.Service) {}
func (m *integrationMockWorker) Run(<-chan interface{}) error { return nil }
func (m *integrationMockWorker) Start() error { return nil }
func (m *integrationMockWorker) Stop() {}

// mockContainerActivity registers a mock for StartContainerActivity that returns a successful result.
func mockContainerActivity(env *testsuite.TestWorkflowEnvironment, stdout string) {
env.OnActivity(activity.StartContainerActivity, mock.Anything, mock.Anything).
Expand Down Expand Up @@ -249,19 +225,3 @@ func TestContainerWithVolumes(t *testing.T) {
require.True(t, env.IsWorkflowCompleted())
require.NoError(t, env.GetWorkflowError())
}

func TestWorkflowRegistration(t *testing.T) {
// Test that workflows and activities can be registered without error
w := &integrationMockWorker{}

// These should not panic
assert.NotPanics(t, func() {
RegisterWorkflows(w)
})
assert.NotPanics(t, func() {
RegisterActivities(w)
})
assert.NotPanics(t, func() {
RegisterAll(w)
})
}
Loading
Loading