mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 08:09:22 +00:00
307 lines
12 KiB
YAML
307 lines
12 KiB
YAML
name: Generate Unit Tests (reusable)
|
|
|
|
# Reusable workflow — called from unit-tests.yml.
|
|
# Must receive caller context as inputs because inside a workflow_call the
|
|
# `github.event_name` is always "workflow_call", not "pull_request"/"push".
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
event_name:
|
|
description: 'Triggering event (pull_request or push)'
|
|
required: true
|
|
type: string
|
|
base_ref:
|
|
description: 'Base branch for comparison (e.g. main)'
|
|
required: false
|
|
type: string
|
|
default: 'main'
|
|
secrets:
|
|
ANTHROPIC_API_KEY:
|
|
required: true
|
|
GITHUB_TOKEN:
|
|
required: false
|
|
|
|
permissions: {}
|
|
|
|
jobs:
|
|
generate:
|
|
runs-on:
|
|
labels: ubicloud-standard-8
|
|
permissions:
|
|
contents: write
|
|
id-token: write
|
|
actions: read
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v5
|
|
with:
|
|
# Full history required to diff against PR base and check git log timestamps.
|
|
fetch-depth: 0
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Get changed web source files
|
|
id: changed_files
|
|
run: |
|
|
# For PRs: compare the full PR branch against its base branch.
|
|
# Handles rebase / force-push correctly — HEAD~1 would be wrong after a rebase.
|
|
# For pushes to main: compare only the latest commit.
|
|
if [ "${{ inputs.event_name }}" = "pull_request" ]; then
|
|
BASE_REF="origin/${{ inputs.base_ref }}"
|
|
else
|
|
BASE_REF="HEAD~1"
|
|
fi
|
|
|
|
echo "Using base ref: $BASE_REF"
|
|
git fetch origin "${{ inputs.base_ref }}" --depth=1 2>/dev/null || true
|
|
|
|
CHANGED=$(git diff --name-only "$BASE_REF" HEAD -- 'web/src/**' \
|
|
| grep -E '\.(ts|vue|js)$' \
|
|
| grep -v '\.spec\.' \
|
|
| grep -v '__tests__' \
|
|
| grep -v '/test/' \
|
|
| grep -v '\.d\.ts$' \
|
|
|| true)
|
|
|
|
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
|
|
echo "$CHANGED" >> $GITHUB_OUTPUT
|
|
echo "EOF" >> $GITHUB_OUTPUT
|
|
|
|
if [ -n "$CHANGED" ]; then
|
|
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
echo "Changed source files requiring test review:"
|
|
echo "$CHANGED"
|
|
else
|
|
echo "has_changes=false" >> $GITHUB_OUTPUT
|
|
echo "No source files changed in web/src/ — skipping test generation"
|
|
fi
|
|
|
|
# Expose resolved base ref for use in the Claude prompt
|
|
echo "base_ref=$BASE_REF" >> $GITHUB_OUTPUT
|
|
|
|
- name: Setup Node.js
|
|
if: steps.changed_files.outputs.has_changes == 'true'
|
|
uses: actions/setup-node@v5
|
|
with:
|
|
node-version: '22'
|
|
cache: 'npm'
|
|
cache-dependency-path: web/package-lock.json
|
|
|
|
- name: Install dependencies
|
|
if: steps.changed_files.outputs.has_changes == 'true'
|
|
working-directory: web
|
|
run: npm ci
|
|
|
|
- name: Configure git
|
|
if: steps.changed_files.outputs.has_changes == 'true'
|
|
run: |
|
|
git config --global user.name 'github-actions[bot]'
|
|
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
|
|
|
- name: Generate/Update Unit Tests with Claude
|
|
if: steps.changed_files.outputs.has_changes == 'true'
|
|
uses: anthropics/claude-code-action@v1
|
|
with:
|
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
track_progress: true
|
|
claude_args: |
|
|
--allowedTools "Read,Write,Edit,Glob,Grep,Bash"
|
|
prompt: |
|
|
You are a senior frontend engineer maintaining unit tests for the OpenObserve web application.
|
|
|
|
## Event Context
|
|
- Event type: ${{ inputs.event_name }}
|
|
- Base ref for diff: ${{ steps.changed_files.outputs.base_ref }}
|
|
|
|
## Changed Source Files
|
|
The following source files changed and need their unit tests reviewed:
|
|
|
|
```
|
|
${{ steps.changed_files.outputs.changed_files }}
|
|
```
|
|
|
|
---
|
|
|
|
## For EACH file above, execute this exact sequence:
|
|
|
|
---
|
|
|
|
### PHASE 1 — Assess (decide what action is needed)
|
|
|
|
**1a. Read the source file completely.**
|
|
|
|
**1b. Get the full diff of what changed in this file:**
|
|
```bash
|
|
git diff ${{ steps.changed_files.outputs.base_ref }} HEAD -- <file_path>
|
|
```
|
|
|
|
**1c. Find the spec file** (same path, `.spec.ts` extension):
|
|
- `web/src/components/Foo.vue` → `web/src/components/Foo.spec.ts`
|
|
- `web/src/utils/helper.ts` → `web/src/utils/helper.spec.ts`
|
|
- `web/src/composables/useFoo.ts` → `web/src/composables/useFoo.spec.ts`
|
|
|
|
**1d. If spec file exists — check if it is stale:**
|
|
```bash
|
|
# When was the test file last updated?
|
|
git log --follow -1 --format="%H %ar %s" -- <spec_file_path>
|
|
# When was the source file last updated?
|
|
git log --follow -1 --format="%H %ar %s" -- <source_file_path>
|
|
```
|
|
|
|
**1e. Make ONE of these four decisions:**
|
|
|
|
| Decision | Condition |
|
|
|----------|-----------|
|
|
| **CREATE** | No spec file exists |
|
|
| **UPDATE** | Spec file exists BUT source changed after last spec update, OR diff reveals new/removed/changed logic not reflected in tests |
|
|
| **CLEANUP-ONLY** | Spec file exists, recently updated, but diff shows some tests now reference removed functionality |
|
|
| **SKIP** | Spec file exists, updated after source, diff shows no logic changes — tests already in sync |
|
|
|
|
If **SKIP**: print `[SKIP] <file> — tests already up-to-date` and move to the next file.
|
|
|
|
---
|
|
|
|
### PHASE 2 — Execute the decision
|
|
|
|
#### For CREATE or UPDATE:
|
|
|
|
Read these real examples to learn the exact coding conventions:
|
|
```bash
|
|
cat web/src/components/iam/organizations/AppOrganizations.spec.ts
|
|
cat web/src/components/ConfirmDialog.spec.ts
|
|
```
|
|
|
|
Then write/update the spec file with this structure:
|
|
```typescript
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { mount, VueWrapper } from "@vue/test-utils";
|
|
import { createStore } from "vuex";
|
|
import ComponentName from "./ComponentName.vue";
|
|
|
|
// Mocks MUST be at top level (hoisted by vitest)
|
|
vi.mock("@/aws-exports", () => ({
|
|
default: { isEnterprise: "false", isCloud: "false" },
|
|
}));
|
|
vi.mock("@/services/someService", () => ({
|
|
default: {
|
|
getItems: vi.fn().mockResolvedValue({ data: [] }),
|
|
},
|
|
}));
|
|
|
|
describe("ComponentName.vue", () => {
|
|
let wrapper: VueWrapper<any>;
|
|
|
|
const createMockStore = (overrides: Record<string, any> = {}) =>
|
|
createStore({
|
|
state: {
|
|
selectedOrganization: { identifier: "default", label: "Default", id: 1 },
|
|
zoConfig: { version: "v0.2.0", ...overrides.zoConfig },
|
|
...overrides.state,
|
|
},
|
|
mutations: {
|
|
setOrg(state: any, org: any) { state.selectedOrganization = org; },
|
|
...overrides.mutations,
|
|
},
|
|
actions: { ...overrides.actions },
|
|
});
|
|
|
|
beforeEach(() => { vi.clearAllMocks(); });
|
|
afterEach(() => { if (wrapper) wrapper.unmount(); });
|
|
|
|
describe("Initialization", () => {
|
|
it("should mount successfully", () => {
|
|
wrapper = mount(ComponentName, { global: { plugins: [createMockStore()] } });
|
|
expect(wrapper.exists()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("Core Functionality", () => { /* ... */ });
|
|
describe("Error Handling", () => { /* ... */ });
|
|
describe("Edge Cases", () => { /* ... */ });
|
|
});
|
|
```
|
|
|
|
**Test every code path introduced by the diff:**
|
|
- ✅ New props/emits added → test them
|
|
- ✅ New methods/computed → test each branch
|
|
- ✅ New API calls → test success + failure
|
|
- ✅ New conditional rendering → test each condition
|
|
- ✅ Changed watcher logic → test old + new trigger conditions
|
|
- ✅ Removed functionality → remove the corresponding tests
|
|
|
|
**Technical rules:**
|
|
- `vi.mock()` calls must be at module top level (before `describe`)
|
|
- Always `vi.clearAllMocks()` in `beforeEach`
|
|
- Always `wrapper.unmount()` in `afterEach`
|
|
- Use `await wrapper.vm.$nextTick()` after state mutations before DOM assertions
|
|
- Use `await flushPromises()` (from `@vue/test-utils`) after async operations
|
|
- Mock child components that have external deps: `{ name: "Foo", template: "<div/>" }`
|
|
|
|
#### For CLEANUP-ONLY:
|
|
|
|
- Remove tests that reference functions/props/events no longer in the source
|
|
- Verify each removed test actually has no corresponding source construct
|
|
- Do NOT remove tests for things that still exist
|
|
|
|
---
|
|
|
|
### PHASE 3 — Self-healing test verification (max 3 attempts)
|
|
|
|
After writing/updating the spec file, run it:
|
|
```bash
|
|
cd web && npx vitest run --reporter=verbose --environment jsdom <path-to-spec-file> 2>&1
|
|
```
|
|
|
|
**Attempt loop (repeat up to 3 times):**
|
|
|
|
- **If all tests pass** → proceed to Phase 4
|
|
- **If tests fail:**
|
|
1. Read the full error output carefully
|
|
2. Identify root cause: wrong mock, missing await, wrong assertion, import error
|
|
3. Fix the specific failing test(s) — do NOT delete tests to make them pass
|
|
4. Re-run the spec file
|
|
5. If still failing after 3 attempts → print `[WARN] Could not fully heal <spec-file> — leaving with best-effort fixes` and proceed
|
|
|
|
**Never commit a spec file that introduces new test failures.**
|
|
|
|
---
|
|
|
|
### PHASE 4 — Commit only if something actually changed
|
|
|
|
After all files have been processed:
|
|
|
|
```bash
|
|
# Check what changed
|
|
git status --short
|
|
|
|
# Stage only spec files
|
|
find web/src -name "*.spec.ts" -exec git add {} \;
|
|
|
|
# Only commit if there are staged changes
|
|
git diff --cached --quiet || git commit -m "test: auto-update unit tests for recent changes"
|
|
|
|
# Push to current branch
|
|
git push origin HEAD
|
|
```
|
|
|
|
---
|
|
|
|
## ABSOLUTE RULES
|
|
|
|
1. **DO NOT skip any file in the list** — assess every one, even if the decision is SKIP
|
|
2. **DO NOT write hollow tests** — every `it()` must have real assertions
|
|
3. **DO NOT delete tests just to fix failures** — fix the root cause
|
|
4. **DO NOT commit if tests fail** — only push when the spec file passes
|
|
5. **ONE commit at the end** — not one commit per file
|
|
6. **DO NOT modify source files** — only touch `*.spec.ts` files
|
|
7. **Run this workflow once** — commit with the exact message above so the bot-actor guard prevents re-triggering
|
|
|
|
## Environment
|
|
|
|
- Setup file: `web/src/test/unit/helpers/setupTests.ts` (auto-loaded)
|
|
- Vitest config: `web/vitest.config.ts`
|
|
- Test environment: jsdom
|
|
- Coverage thresholds: 33% lines/functions/statements, 40% branches
|
|
- Stack: Vue 3 + Vuex 4 + Vue Router 4 + vue-i18n 9 + Quasar 2 + Vitest + @vue/test-utils 2
|
|
- All `npm` commands must be run from the `web/` directory |