Skip to main content
Glama
README.md7.09 kB
# SI Function Tester A testing framework for System Initiative functions that allows you to write unit tests with mocked external dependencies. ## What is This? SI Function Tester lets you test your System Initiative functions **without needing AWS credentials or real cloud resources**. It mocks external dependencies like `siExec` (AWS CLI calls) and `requestStorage`, so tests run fast and reliably in isolation. ## Features - ✅ **Mock AWS CLI calls** - No real AWS credentials needed - ✅ **Fast execution** - Tests run in milliseconds - ✅ **Type-safe** - Full TypeScript support - ✅ **Convention-based** - Automatically pairs `*.test.ts` with `*.ts` files - ✅ **All function types** - Actions, Management, Codegen, Qualifications, Attributes, Authentication - ✅ **Persistent server** - Keep Docker container running for fast iterative testing - ✅ **Simple command** - Single `si-test <directory>` command ## Quick Start ### 1. Download the `si-test` script ```bash # Download the script curl -O https://raw.githubusercontent.com/systeminit/si/main/bin/si-function-tester/si-test # Make it executable chmod +x si-test # Optionally, move to your PATH sudo mv si-test /usr/local/bin/ # OR for user-only install mkdir -p ~/bin && mv si-test ~/bin/ ``` ### 2. Start the test server ```bash # Pull and start the server (once per session) docker run -d -p 8081:8081 --name si-function-tester systeminit/si-function-tester:stable # Verify it's running curl http://localhost:8081/health ``` ### 3. Run tests ```bash # Run tests on a directory (automatically finds test pairs) si-test /path/to/your/functions # Or test a specific pair si-test examples/attribute ``` **Output:** ``` Scanning directory: examples/ Testing: attribute [attribute] ✓ returns most recent AMI matching filters (88.3ms) ✓ returns empty string when no filters specified (88.6ms) Test Results: 2 passed, 0 failed All tests passed! SUMMARY Total test pairs: 1 Passed: 1 ✓ All test pairs passed! ``` ## Convention-Based Testing **File naming is important!** The test runner automatically pairs files based on a naming convention: - `<name>.ts` - Your function implementation - `<name>.test.ts` - Your test suite Both files must be in the same directory. For example: ``` my-functions/ ├── create.ts ← Function ├── create.test.ts ← Tests for create.ts ├── update.ts ← Function └── update.test.ts ← Tests for update.ts ``` When you run `./si-test my-functions/`, it will: 1. Find all `*.test.ts` files recursively 2. Look for the corresponding `*.ts` file in the same directory 3. Run tests for each pair found **Skipped pairs:** If a `.test.ts` file exists without a matching `.ts` file, it will be skipped with a warning. ## API Reference ### `defineTests(tests: TestSuite)` Define a test suite with one or more test cases. ```typescript export default defineTests({ "test name": { input: { properties: { /* ... */ } }, mocks: { /* ... */ }, expect: { /* ... */ }, timeout?: number, skip?: boolean } }); ``` ### Test Case Structure ```typescript interface TestCase { // Input to your function input: { properties?: Record<string, unknown>; [key: string]: unknown; }; // Mock configurations mocks?: { exec?: ExecMockBuilder; storage?: Record<string, unknown>; }; // Expected output expect: { status?: "ok" | "warning" | "error" | Matcher<string>; payload?: unknown; resourceId?: string | null | Matcher<string | null>; message?: string | Matcher<string>; validate?: (result: ActionResult) => boolean | void; }; // Test timeout in milliseconds (default: 5000) timeout?: number; // Skip this test skip?: boolean; } ``` ### Mocking `siExec` Mock external command execution: ```typescript mocks: { exec: mockExec() .command("aws s3 mb") // String match .returns({ stdout: "make_bucket: my-bucket", stderr: "", exitCode: 0, }) .command(/terraform apply/) // Regex match .returns({ stdout: "Applied", exitCode: 0 }) .command("failing command") .throws(new Error("Command failed")); } ``` **Sequential mocks** (for multiple calls to same command): ```typescript mocks: { exec: mockExec() .command("aws cloudcontrol get-resource-request-status") .returns({ stdout: '{"Status":"IN_PROGRESS"}', exitCode: 0 }) .command("aws cloudcontrol get-resource-request-status") .returns({ stdout: '{"Status":"SUCCESS"}', exitCode: 0 }); } ``` ### Matchers Use matcher functions for flexible assertions: ```typescript expect: { status: "ok", message: expect.stringContaining("success"), resourceId: expect.string(), payload: expect.objectContaining({ id: "123" }) } ``` **Available matchers:** - `expect.string()` - Any string - `expect.stringContaining(substring)` - String containing substring - `expect.stringMatching(regex)` - String matching regex - `expect.object()` - Any object - `expect.objectContaining(keys)` - Object containing specific keys - `expect.number()` - Any number - `expect.boolean()` - Any boolean - `expect.array()` - Any array - `expect.null()` - null - `expect.undefined()` - undefined - `expect.any()` - Any value ### Custom Validation Write custom validation logic for complex checks: ```typescript expect: { validate: (result) => { const payload = result.payload as { arn: string }; if (!payload.arn.startsWith("arn:aws:s3:::")) { throw new Error("Invalid S3 ARN format"); } // Returning nothing or true means validation passed }; } ``` ### Mocking `requestStorage` Pre-populate storage for stateful tests: ```typescript mocks: { storage: { previousValue: 42, operationId: "op-123" } } ``` Access storage in validation: ```typescript expect: { validate: (result) => { const storage = requestStorage._inspect(); if (storage.data.counter !== 43) { throw new Error("Counter not incremented"); } }; } ``` ## Examples See complete working examples in [`examples/`](examples/): - **actions/** - Action functions (create, update, delete resources) with retries and polling - **attribute/** - Attribute functions (compute derived values from component properties) - **authentication/** - Authentication functions (set credentials and environment variables) - **codegen/** - Codegen functions (generate CloudControl API payloads) - **management/** - Management functions (import, refresh resources) - **qualifications/** - Qualification functions (validate component state) **Run all examples:** ```bash ./si-test examples/ ``` This will run 26 tests across all 6 function types! ## Import Path Always use this import path in your test files: ```typescript import { defineTests, mockExec, expect } from "file:///app/index.ts"; ``` This works both in Docker and when running tests with Deno directly. ## Documentation - **[USAGE.md](USAGE.md)** - Build and usage instructions ## License See LICENSE in the root of the SI repository.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server