test-unit — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited test-unit (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Write effective, maintainable unit tests informed by the project's actual framework, production error data, and current best practices.
Before writing any tests, discover the project's testing setup.
Read the dependency manifest (package.json, requirements.txt, pyproject.toml, go.mod, Cargo.toml, build.gradle, Gemfile) and look for:
| Framework | Detection Signal |
|---|---|
| Vitest | vitest in devDependencies, vitest.config.ts |
| Jest | jest in devDependencies, jest.config.*, "jest" key in package.json |
| Testing Library | @testing-library/react, @testing-library/vue, etc. |
| Playwright | @playwright/test in devDependencies, playwright.config.ts |
| Cypress | cypress in devDependencies, cypress.config.* |
| pytest | pytest in requirements, conftest.py files |
| Go test | _test.go files, go test in Makefile |
| RSpec | rspec in Gemfile, spec/ directory |
| JUnit | junit in build.gradle, src/test/ directory |
Glob: **/*.test.{ts,tsx,js,jsx} → JS/TS test files
Glob: **/*.spec.{ts,tsx,js,jsx} → JS/TS spec files
Glob: **/test_*.py → Python test files
Glob: **/*_test.go → Go test files
Glob: **/spec/**/*_spec.rb → Ruby spec filesRead 2-3 existing test files to understand:
describe/it vs test)vi.mock, jest.mock, unittest.mock)beforeEach, afterEach, fixtures)expect, assert, chai)Glob: **/vitest.config.*
Glob: **/jest.config.*
Glob: **/pytest.ini
Glob: **/conftest.py
Glob: **/setup.cfg
Glob: **/.nycrc*
Glob: **/c8.config.*Extract: coverage thresholds, test directories, global setup files, module aliases.
If there's a coverage tool configured (c8, istanbul/nyc, coverage.py):
# Check if coverage script exists
Grep: "coverage" in package.json scripts section
Grep: "c8" or "nyc" or "istanbul" in package.jsonTEST ENVIRONMENT:
- Framework: [Vitest/Jest/pytest/etc. + version]
- Assertion style: [expect/assert/chai]
- Component testing: [Testing Library/Enzyme/none]
- Mock pattern: [vi.mock/jest.mock/unittest.mock]
- Coverage tool: [c8/nyc/coverage.py/none]
- Coverage threshold: [X% or not configured]
- Test directory: [__tests__/tests/spec/co-located]
- Existing tests: [count]
- Config file: [path]Before writing tests, research current best practices for the detected framework.
Fetch docs for the detected test framework:
CallMcpTool(server: "context7", toolName: "resolve-library-id", arguments: {
"libraryName": "<DETECTED_FRAMEWORK>",
"query": "unit testing best practices"
})Then fetch specific guidance:
CallMcpTool(server: "context7", toolName: "query-docs", arguments: {
"libraryId": "<RESOLVED_ID>",
"query": "testing patterns mocking setup teardown"
})Do this for each major testing dependency (e.g., vitest, @testing-library/react, msw).
CallMcpTool(server: "user-firecrawl", toolName: "firecrawl_search", arguments: {
"query": "<FRAMEWORK> unit testing best practices [current year]",
"limit": 5,
"sources": [{ "type": "web" }]
})Run 2-3 searches targeting different aspects:
| Search | Query |
|---|---|
| Best practices | <framework> testing best practices [current year] |
| Mocking patterns | <framework> mocking API calls best practices |
| Component testing | React Testing Library component testing patterns [current year] |
| Coverage strategy | unit test coverage strategy meaningful tests vs coverage percentage |
Scrape the 1-2 most authoritative results for implementation details:
CallMcpTool(server: "user-firecrawl", toolName: "firecrawl_scrape", arguments: {
"url": "<AUTHORITATIVE_URL>",
"formats": ["markdown"],
"onlyMainContent": true
})Use Sentry MCP to find production errors that should have been caught by tests.
CallMcpTool(server: "plugin-sentry-sentry", toolName: "search_issues", arguments: {
"organizationSlug": "<ORG_SLUG>",
"naturalLanguageQuery": "unresolved errors from the last 30 days",
"projectSlugOrId": "<PROJECT_SLUG>",
"regionUrl": "<REGION_URL>",
"limit": 25
})For each Sentry error:
Glob: **/<filename>.test.*)COVERAGE GAP ANALYSIS:
- Production errors without tests: [count]
1. [file] — [error type] — [frequency] — NO TEST FILE
2. [file] — [error type] — [frequency] — test exists but missing edge case
- Highest-impact files to test: [ordered list]describe('ComponentOrModule', () => {
describe('methodOrBehavior', () => {
it('should [expected behavior] when [condition]', () => {
// Arrange — set up test data and dependencies
const input = createTestData();
// Act — execute the behavior under test
const result = functionUnderTest(input);
// Assert — verify the outcome
expect(result).toEqual(expectedOutput);
});
});
});Test file names — co-locate with source or mirror directory structure:
| Source File | Test File |
|---|---|
src/utils/formatDate.ts | src/utils/formatDate.test.ts |
src/components/Button.tsx | src/components/Button.test.tsx |
app/services/user.py | tests/services/test_user.py |
Test descriptions — use should [behavior] when [condition]:
describe('validateEmail', () => {
it('should return true for valid email', () => { /* ... */ });
it('should return false when @ is missing', () => { /* ... */ });
it('should return false for empty string', () => { /* ... */ });
it('should handle unicode characters in local part', () => { /* ... */ });
});DO test:
DON'T test:
For every function, consider:
| Category | Test Values |
|---|---|
| Empty | null, undefined, "", [], {} |
| Boundary | 0, -1, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER |
| Type confusion | String where number expected, array where object expected |
| Unicode | Emoji, CJK characters, RTL text, zero-width spaces |
| Concurrency | Rapid successive calls, race conditions |
| Network | Timeout, 404, 500, empty response, malformed JSON |
| Dates | Midnight, DST transitions, leap years, timezone boundaries |
describe('formatCurrency', () => {
it('should format positive amounts', () => {
expect(formatCurrency(1234.5)).toBe('$1,234.50');
});
it('should handle zero', () => {
expect(formatCurrency(0)).toBe('$0.00');
});
it('should handle negative amounts', () => {
expect(formatCurrency(-100)).toBe('-$100.00');
});
it('should handle very large numbers', () => {
expect(formatCurrency(999999999.99)).toBe('$999,999,999.99');
});
});describe('fetchUser', () => {
it('should return user data for valid id', async () => {
const user = await fetchUser('123');
expect(user).toEqual({ id: '123', name: 'John Doe' });
});
it('should throw for non-existent user', async () => {
await expect(fetchUser('invalid')).rejects.toThrow('User not found');
});
it('should handle network timeout', async () => {
vi.useFakeTimers(); // or jest.useFakeTimers()
const promise = fetchUser('123');
vi.advanceTimersByTime(30000);
await expect(promise).rejects.toThrow('timeout');
vi.useRealTimers();
});
});// Vitest
import { vi } from 'vitest';
vi.mock('./emailService', () => ({
sendEmail: vi.fn().mockResolvedValue({ success: true }),
}));
// Jest
jest.mock('./emailService');
// MSW (API mocking — preferred for HTTP)
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('/api/users/:id', ({ params }) => {
return HttpResponse.json({ id: params.id, name: 'Test User' });
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('LoginForm', () => {
it('should submit with valid credentials', async () => {
const onSubmit = vi.fn();
const user = userEvent.setup();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/email/i), '[email protected]');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'password123',
});
});
});
it('should show validation error for invalid email', async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={vi.fn()} />);
await user.type(screen.getByLabelText(/email/i), 'invalid');
await user.click(screen.getByRole('button', { name: /sign in/i }));
expect(screen.getByText(/valid email/i)).toBeInTheDocument();
});
it('should disable submit button while loading', () => {
render(<LoginForm onSubmit={vi.fn()} isLoading />);
expect(screen.getByRole('button', { name: /sign in/i })).toBeDisabled();
});
});import { renderHook, act, waitFor } from '@testing-library/react';
describe('useCounter', () => {
it('should start with initial value', () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
it('should increment', () => {
const { result } = renderHook(() => useCounter(0));
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
it('should not go below zero', () => {
const { result } = renderHook(() => useCounter(0));
act(() => result.current.decrement());
expect(result.current.count).toBe(0);
});
});describe('POST /api/users', () => {
it('should create user with valid data', async () => {
const req = new Request('http://localhost/api/users', {
method: 'POST',
body: JSON.stringify({ name: 'Test', email: '[email protected]' }),
});
const response = await POST(req);
const data = await response.json();
expect(response.status).toBe(201);
expect(data).toMatchObject({ name: 'Test', email: '[email protected]' });
});
it('should return 422 for invalid email', async () => {
const req = new Request('http://localhost/api/users', {
method: 'POST',
body: JSON.stringify({ name: 'Test', email: 'invalid' }),
});
const response = await POST(req);
expect(response.status).toBe(422);
});
});const createUser = (overrides: Partial<User> = {}): User => ({
id: crypto.randomUUID(),
name: 'Test User',
email: '[email protected]',
role: 'user',
createdAt: new Date('2024-01-01'),
...overrides,
});
// Usage
const admin = createUser({ role: 'admin' });
const inactive = createUser({ status: 'inactive', name: 'Inactive User' });// Bad — meaningless
const data = { a: 'b', c: 'd' };
// Good — realistic and descriptive
const user = { name: 'Jane Doe', email: '[email protected]', role: 'admin' };~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.