Skip to content
Closed
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
125 changes: 125 additions & 0 deletions .github/workflows/backend-unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# This workflow runs tests to verify all the API Server REST Endpoints
name: Backend Unit Tests
env:
TESTS_DIR: "./components/backend/handlers"
TESTS_LABEL: "unit"
JUNIT_FILENAME: "junit.xml"

on:
push:
branches: [main]

workflow_dispatch:
inputs:
test_label:
description: "Test label that you want to filter on and run"
default: 'unit'
required: true
type: string
default_namespace:
description: "Default namespace for testing"
default: 'test-namespace'
required: false
type: string

pull_request:
paths:
- '.github/workflows/backend-unit-tests.yml'
- './components/backend/**'
- '!**/*.md'

concurrency:
group: backend-unit-tests-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
backend-unit-test:
runs-on: ubuntu-latest
name: Ambient Code Backend Unit Tests

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Create reports directory
shell: bash
working-directory: ${{ env.TESTS_DIR }}
run: |
mkdir -p reports

- name: Configure Input Variables
shell: bash
id: configure
run: |
TEST_LABEL=${{ env.TESTS_LABEL }}
DEFAULT_NAMESPACE="test-namespace"
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
TEST_LABEL=${{ inputs.test_label }}
DEFAULT_NAMESPACE=${{ inputs.default_namespace }}
fi

{
echo "TEST_LABEL=$TEST_LABEL"
echo "DEFAULT_NAMESPACE=$DEFAULT_NAMESPACE"
} >> "$GITHUB_OUTPUT"

- name: Run Tests
id: run-tests
shell: bash
working-directory: ${{ env.TESTS_DIR }}
run: |
go run github.com/onsi/ginkgo/v2/ginkgo -r -v --cover --keep-going --github-output=true --tags=test --label-filter=${{ steps.configure.outputs.TEST_LABEL }} --junit-report=${{ env.JUNIT_FILENAME }} --output-dir=reports -- -testNamespace=${{ steps.configure.outputs.DEFAULT_NAMESPACE }}
continue-on-error: true

- name: Install Junit2Html plugin and generate report
if: (!cancelled())
shell: bash
run: |
pip install junit2html
junit2html ${{ env.TESTS_DIR }}/reports/${{ env.JUNIT_FILENAME }} ${{ env.TESTS_DIR }}/reports/test-report.html
continue-on-error: true

- name: Configure report name
id: name_gen
shell: bash
run: |
uuid=$(uuidgen)
REPORT_NAME="Backend Unit Tests HTML Report - ${{ github.run_id }}_${{ github.job }}_$uuid"
echo "REPORT_NAME=$REPORT_NAME" >> "$GITHUB_OUTPUT"

- name: Upload HTML Report
id: upload
uses: actions/upload-artifact@v4
if: (!cancelled())
with:
name: ${{ steps.name_gen.outputs.REPORT_NAME }}
path: ${{ env.TESTS_DIR }}/reports/test-report.html
retention-days: 7
continue-on-error: true

- name: Publish Test Summary With HTML Report
id: publish
uses: kubeflow/pipelines/.github/actions/junit-summary@master
if: (!cancelled()) && steps.upload.outcome != 'failure'
with:
xml_files: '${{ env.TESTS_DIR }}/reports'
custom_data: '{\"HTML Report\": \"${{ steps.upload.outputs.artifact-url }}\"}'
continue-on-error: true

- name: Publish Test Summary
id: summary
uses: kubeflow/pipelines/.github/actions/junit-summary@master
if: (!cancelled()) && steps.upload.outcome == 'failure'
with:
xml_files: '${{ env.TESTS_DIR }}/reports'
continue-on-error: true

- name: Mark Workflow failure if test step failed
if: steps.run-tests.outcome != 'success' && !cancelled()
shell: bash
run: exit 1

- name: Mark Workflow failure if test reporting failed
if: (steps.publish.outcome == 'failure' || steps.summary.outcome == 'failure' || steps.upload.outcome != 'success') && !cancelled()
shell: bash
run: exit 1
9 changes: 9 additions & 0 deletions .github/workflows/go-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ jobs:
working-directory: components/backend
args: --timeout=5m

# The backend unit tests require -tags=test (to compile test-only hooks used by handlers tests).
# We lint both production build (default) and test build (with tags) to avoid hiding issues.
- name: Run golangci-lint (test build tags)
uses: golangci/golangci-lint-action@v9
with:
version: latest
working-directory: components/backend
args: --timeout=5m --build-tags=test

lint-operator:
runs-on: ubuntu-latest
needs: detect-go-changes
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/test-local-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'components/backend/go.mod'
cache-dependency-path: 'components/backend/go.sum'

- name: Install minikube and kubectl
run: |
Expand Down Expand Up @@ -58,6 +64,29 @@ jobs:
kubectl describe deployment agentic-operator -n ambient-code | tail -50
exit 1
}

- name: Run backend integration tests (real k8s auth path)
run: |
set -euo pipefail

echo "Setting up ServiceAccount + RBAC for backend integration tests..."
kubectl -n ambient-code create serviceaccount backend-integration-test 2>/dev/null || true
kubectl -n ambient-code create role backend-integration-test \
--verb=get,list \
--resource=configmaps 2>/dev/null || true
kubectl -n ambient-code create rolebinding backend-integration-test \
--role=backend-integration-test \
--serviceaccount=ambient-code:backend-integration-test 2>/dev/null || true

TEST_TOKEN="$(kubectl -n ambient-code create token backend-integration-test)"
echo "::add-mask::$TEST_TOKEN"

echo "Running Go integration tests (skips any external-provider tests without env)..."
cd components/backend
INTEGRATION_TESTS=true \
K8S_TEST_TOKEN="$TEST_TOKEN" \
K8S_TEST_NAMESPACE="ambient-code" \
go test ./tests/integration/... -count=1 -timeout=10m

- name: Run Makefile smoke tests
run: |
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,7 @@ e2e/langfuse/.env.langfuse-keys
# AI assistant configuration
.cursor/
.tessl/

# Test Reporting
logs/
reports/
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ local-test-quick: check-kubectl check-minikube ## Quick smoke test of local envi
@echo "$(COLOR_BLUE)β–Ά$(COLOR_RESET) Testing namespace..."
@kubectl get namespace $(NAMESPACE) >/dev/null 2>&1 && echo "$(COLOR_GREEN)βœ“$(COLOR_RESET) Namespace exists" || (echo "$(COLOR_RED)βœ—$(COLOR_RESET) Namespace missing" && exit 1)
@echo "$(COLOR_BLUE)β–Ά$(COLOR_RESET) Waiting for pods to be ready..."
@kubectl wait --for=condition=ready pod -l app=backend -n $(NAMESPACE) --timeout=60s >/dev/null 2>&1 && \
@kubectl wait --for=condition=ready pod -l app=backend-api -n $(NAMESPACE) --timeout=60s >/dev/null 2>&1 && \
kubectl wait --for=condition=ready pod -l app=frontend -n $(NAMESPACE) --timeout=60s >/dev/null 2>&1 && \
echo "$(COLOR_GREEN)βœ“$(COLOR_RESET) Pods ready" || (echo "$(COLOR_RED)βœ—$(COLOR_RESET) Pods not ready" && exit 1)
@echo "$(COLOR_BLUE)β–Ά$(COLOR_RESET) Testing backend health..."
Expand Down
6 changes: 6 additions & 0 deletions components/backend/.golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ linters:
- staticcheck
- govet

# Exclude style checks from test utilities (common patterns in test code)
- path: tests/.*\.go
linters:
- staticcheck
text: "(should not use|should not use dot imports|should not use ALL_CAPS|at least one file)"

# Allow type assertions in K8s unstructured object parsing (intentional pattern)
- path: (handlers|jira)/.*\.go
text: "type assertion"
Expand Down
53 changes: 49 additions & 4 deletions components/backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,62 @@ clean: ## Clean build artifacts
# Test targets
test: test-unit test-contract ## Run all tests (excluding integration tests)

test-unit: ## Run unit tests
go test ./tests/unit/... -v
test-unit: ## Run unit tests using Ginkgo
@echo "Running unit tests with Ginkgo..."
ginkgo run --label-filter="unit" --junit-report=reports/junit.xml --json-report=reports/results.json test/unit

test-unit-go: ## Run unit tests with go test (alternative)
go test -v -tags=test ./handlers ./types ./git -timeout=5m

test-contract: ## Run contract tests
go test ./tests/contract/... -v

test-integration: ## Run integration tests (requires Kubernetes cluster)
@echo "Running integration tests (requires Kubernetes cluster access)..."
go test ./tests/integration/... -v -timeout=5m
USE_REAL_CLUSTER=true CLEANUP_RESOURCES=true ginkgo run --label-filter="integration" --timeout=10m

test-integration-short: ## Run integration tests with short timeout
go test ./tests/integration/... -v -short

test-all: test test-integration ## Run all tests including integration tests

# Ginkgo-specific test targets
test-ginkgo: ## Run all tests using Ginkgo framework
@echo "Running all tests with Ginkgo..."
ginkgo run --junit-report=reports/junit.xml --json-report=reports/results.json

test-ginkgo-parallel: ## Run tests in parallel
@echo "Running tests in parallel..."
ginkgo run -p --junit-report=reports/junit.xml --json-report=reports/results.json

test-ginkgo-verbose: ## Run tests with verbose output
@echo "Running tests with verbose output..."
ginkgo run -v --junit-report=reports/junit.xml --json-report=reports/results.json

test-handlers: ## Run handler tests only
@echo "Running handler tests..."
ginkgo run --tags=test --label-filter="handlers" -v

test-types: ## Run type tests only
@echo "Running type tests..."
ginkgo run --tags=test --label-filter="types" -v

test-git: ## Run git operation tests only
@echo "Running git operation tests..."
ginkgo run --tags=test --label-filter="git" -v

test-fast: ## Run tests excluding slow ones
@echo "Running fast tests only..."
SKIP_SLOW_TESTS=true ginkgo run --tags=test --label-filter="!slow"

test-auth: ## Run authentication and authorization tests
@echo "Running auth tests..."
ginkgo run --tags=test --label-filter="auth" -v

test-focus: ## Run specific test by pattern (usage: make test-focus FOCUS="test pattern")
@echo "Running focused tests: $(FOCUS)"
ginkgo run --focus="$(FOCUS)" -v

# Test with specific configuration
test-integration-local: ## Run integration tests with local configuration
@echo "Running integration tests with local configuration..."
Expand Down Expand Up @@ -88,7 +129,10 @@ vet: ## Run go vet
go vet ./...

lint: ## Run golangci-lint (requires golangci-lint to be installed)
golangci-lint run
# Lint production build
golangci-lint run --timeout=5m
# Lint test build (handlers unit tests use -tags=test for test-only hooks)
golangci-lint run --timeout=5m --build-tags=test

# Dependency management
deps: ## Download dependencies
Expand All @@ -106,6 +150,7 @@ install-tools: ## Install development tools
@echo "Installing development tools..."
go install github.com/cosmtrek/air@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install github.com/onsi/ginkgo/v2/ginkgo@latest

# Kubernetes-specific targets for integration testing
k8s-setup: ## Setup local Kubernetes for testing (requires kubectl and kind)
Expand Down
45 changes: 45 additions & 0 deletions components/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,51 @@ make run
make dev
```

### Local development authentication (DISABLE_AUTH removed)

The backend **does not** support authentication bypass via `DISABLE_AUTH` (or any env-var based bypass).
All authenticated endpoints require a valid Kubernetes/OpenShift token passed via:

- `Authorization: Bearer <token>` (preferred)
- `X-Forwarded-Access-Token: <token>` (when running behind an auth proxy)

#### Option A: OpenShift / CRC (recommended for this repo)

```bash
# Login and obtain a user token
oc login ...
export OC_TOKEN="$(oc whoami -t)"

# Example request
curl -H "Authorization: Bearer ${OC_TOKEN}" \
http://localhost:8080/health
```

#### Option B: kind/minikube (ServiceAccount token for local dev)

```bash
export DEV_NS=ambient-code
kubectl create namespace "${DEV_NS}" 2>/dev/null || true

kubectl -n "${DEV_NS}" create serviceaccount backend-dev 2>/dev/null || true

# Minimal example permissions (adjust as needed)
kubectl -n "${DEV_NS}" create role backend-dev \
--verb=get,list,watch,create,update,patch,delete \
--resource=secrets,configmaps,services,pods,rolebindings 2>/dev/null || true

kubectl -n "${DEV_NS}" create rolebinding backend-dev \
--role=backend-dev \
--serviceaccount="${DEV_NS}:backend-dev" 2>/dev/null || true

export DEV_TOKEN="$(kubectl -n "${DEV_NS}" create token backend-dev)"

curl -H "Authorization: Bearer ${DEV_TOKEN}" \
http://localhost:8080/health
```

> Tip: If you’re running the backend in-cluster (e.g. via `make dev-start`), your client must still send a token; the backend will never fall back to the in-cluster service account for user-initiated operations.

### Build

```bash
Expand Down
Loading
Loading