diff --git a/skills/pytest/SKILL.md b/skills/pytest/SKILL.md index 35f4e04b40..199d47d49a 100644 --- a/skills/pytest/SKILL.md +++ b/skills/pytest/SKILL.md @@ -1,8 +1,6 @@ --- name: pytest -description: > - Pytest testing patterns for Python. - Trigger: When writing or refactoring pytest tests (fixtures, mocking, parametrize, markers). For Prowler-specific API/SDK testing conventions, also use prowler-test-api or prowler-test-sdk. +description: "Trigger: When writing or refactoring pytest tests in Python, including fixtures, mocking, parametrization, async tests, and markers. Provides generic pytest structure before component-specific API or SDK rules." license: Apache-2.0 metadata: author: prowler-cloud @@ -12,183 +10,49 @@ metadata: allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task --- -## Basic Test Structure +## Activation Contract -```python -import pytest +Use this skill for generic pytest structure and patterns; if the test touches Prowler API or SDK specifics, pair it with `prowler-test-api` or `prowler-test-sdk`. -class TestUserService: - def test_create_user_success(self): - user = create_user(name="John", email="john@test.com") - assert user.name == "John" - assert user.email == "john@test.com" +## Hard Rules - def test_create_user_invalid_email_fails(self): - with pytest.raises(ValueError, match="Invalid email"): - create_user(name="John", email="invalid") -``` +- Keep tests behavior-focused and name them after expected outcomes. +- Extract reusable setup into fixtures instead of repeating inline construction. +- Use `pytest.raises` for failure expectations and `@pytest.mark.parametrize` for matrix coverage. +- Mock external boundaries, not the logic under test. +- Register and use markers intentionally; do not invent silent marker names. +- Prefer local references only; do not rely on external documentation links inside the skill. -## Fixtures +## Decision Gates -```python -import pytest +| Question | Action | +|---|---| +| Shared setup across tests? | Move it into a fixture or `conftest.py`. | +| Same assertion logic over many inputs? | Use `@pytest.mark.parametrize`. | +| Need to verify an exception? | Use `pytest.raises(..., match=...)`. | +| Testing async behavior? | Use `@pytest.mark.asyncio` or the repo's async test pattern. | +| Working in `api/` or `prowler/`? | Load the component-specific testing skill too. | -@pytest.fixture -def user(): - """Create a test user.""" - return User(name="Test User", email="test@example.com") +## Execution Steps -@pytest.fixture -def authenticated_client(client, user): - """Client with authenticated user.""" - client.force_login(user) - return client +1. Identify whether the test is generic pytest, API-specific, or SDK-specific. +2. Read neighboring tests and `conftest.py` before adding new fixtures. +3. Write focused test functions or test classes with clear outcome-based names. +4. Promote repeated setup into fixtures and shared helpers only when duplication appears twice or more. +5. Use parametrization, markers, and mocks deliberately to keep coverage broad but readable. +6. Run the narrowest relevant pytest target and inspect failures before widening scope. +7. Report the exact command used and any fixture or marker introduced. -# Fixture with teardown -@pytest.fixture -def temp_file(): - path = Path("/tmp/test_file.txt") - path.write_text("test content") - yield path # Test runs here - path.unlink() # Cleanup after test +## Output Contract -# Fixture scopes -@pytest.fixture(scope="module") # Once per module -@pytest.fixture(scope="class") # Once per class -@pytest.fixture(scope="session") # Once per test session -``` - -## conftest.py - -```python -# tests/conftest.py - Shared fixtures -import pytest - -@pytest.fixture -def db_session(): - session = create_session() - yield session - session.rollback() - -@pytest.fixture -def api_client(): - return TestClient(app) -``` - -## Mocking - -```python -from unittest.mock import patch, MagicMock - -class TestPaymentService: - def test_process_payment_success(self): - with patch("services.payment.stripe_client") as mock_stripe: - mock_stripe.charge.return_value = {"id": "ch_123", "status": "succeeded"} - - result = process_payment(amount=100) - - assert result["status"] == "succeeded" - mock_stripe.charge.assert_called_once_with(amount=100) - - def test_process_payment_failure(self): - with patch("services.payment.stripe_client") as mock_stripe: - mock_stripe.charge.side_effect = PaymentError("Card declined") - - with pytest.raises(PaymentError): - process_payment(amount=100) - -# MagicMock for complex objects -def test_with_mock_object(): - mock_user = MagicMock() - mock_user.id = "user-123" - mock_user.name = "Test User" - mock_user.is_active = True - - result = get_user_info(mock_user) - assert result["name"] == "Test User" -``` - -## Parametrize - -```python -@pytest.mark.parametrize("input,expected", [ - ("hello", "HELLO"), - ("world", "WORLD"), - ("pytest", "PYTEST"), -]) -def test_uppercase(input, expected): - assert input.upper() == expected - -@pytest.mark.parametrize("email,is_valid", [ - ("user@example.com", True), - ("invalid-email", False), - ("", False), - ("user@.com", False), -]) -def test_email_validation(email, is_valid): - assert validate_email(email) == is_valid -``` - -## Markers - -```python -# pytest.ini or pyproject.toml -[tool.pytest.ini_options] -markers = [ - "slow: marks tests as slow", - "integration: marks integration tests", -] - -# Usage -@pytest.mark.slow -def test_large_data_processing(): - ... - -@pytest.mark.integration -def test_database_connection(): - ... - -@pytest.mark.skip(reason="Not implemented yet") -def test_future_feature(): - ... - -@pytest.mark.skipif(sys.platform == "win32", reason="Unix only") -def test_unix_specific(): - ... - -# Run specific markers -# pytest -m "not slow" -# pytest -m "integration" -``` - -## Async Tests - -```python -import pytest - -@pytest.mark.asyncio -async def test_async_function(): - result = await async_fetch_data() - assert result is not None -``` - -## Commands - -```bash -pytest # Run all tests -pytest -v # Verbose output -pytest -x # Stop on first failure -pytest -k "test_user" # Filter by name -pytest -m "not slow" # Filter by marker -pytest --cov=src # With coverage -pytest -n auto # Parallel (pytest-xdist) -pytest --tb=short # Short traceback -``` +- State whether the change relied on fixtures, parametrization, mocking, markers, or async support. +- Mention any component-specific skill paired with pytest. +- Report the exact pytest command used for validation. +- Call out any test isolation or fixture-scope decision that affects future contributors. ## References -For general pytest documentation, see: -- **Official Docs**: https://docs.pytest.org/en/stable/ - -For Prowler SDK testing with provider-specific patterns (moto, MagicMock), see: -- **Documentation**: [references/prowler-testing.md](references/prowler-testing.md) +- [TDD skill](../tdd/SKILL.md) +- [Prowler API testing skill](../prowler-test-api/SKILL.md) +- [Prowler SDK testing skill](../prowler-test-sdk/SKILL.md) +- [Repository agent rules](../../AGENTS.md) diff --git a/skills/react-19/SKILL.md b/skills/react-19/SKILL.md index 519ae9f15e..0880757d82 100644 --- a/skills/react-19/SKILL.md +++ b/skills/react-19/SKILL.md @@ -1,8 +1,6 @@ --- name: react-19 -description: > - React 19 patterns with React Compiler. - Trigger: When writing React 19 components/hooks in .tsx (React Compiler rules, hook patterns, refs as props). If using Next.js App Router/Server Actions, also use nextjs-15. +description: "Trigger: When writing React 19 components, hooks, or `.tsx` files, especially with React Compiler, `use()`, actions, or ref-as-prop patterns. Applies React 19 runtime and composition rules." license: Apache-2.0 metadata: author: prowler-cloud @@ -12,113 +10,46 @@ metadata: allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task --- -## No Manual Memoization (REQUIRED) +## Activation Contract -```typescript -// ✅ React Compiler handles optimization automatically -function Component({ items }) { - const filtered = items.filter(x => x.active); - const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name)); +Use this skill when the change is inside React 19 component code and the agent must choose between Server Components, Client Components, compiler-friendly patterns, or modern hook APIs. - const handleClick = (id) => { - console.log(id); - }; +## Hard Rules - return ; -} +- Do not add `useMemo` or `useCallback` for routine render-path optimization; React Compiler handles the common case. +- Prefer Server Components by default; add `"use client"` only for client-only behavior. +- Import named React APIs; do not use default `React` imports. +- Use `ref` as a prop in React 19 instead of introducing `forwardRef` by habit. +- If the task also involves App Router or Server Actions integration details, load `nextjs-15` too. -// ❌ NEVER: Manual memoization -const filtered = useMemo(() => items.filter(x => x.active), [items]); -const handleClick = useCallback((id) => console.log(id), []); -``` +## Decision Gates -## Imports (REQUIRED) +| Question | Action | +|---|---| +| Does the component use state, effects, browser APIs, or event handlers? | Mark it as a Client Component with `"use client"`. | +| Does the component only fetch or compose data for rendering? | Keep it as a Server Component. | +| Are you reading a promise or conditional context? | Consider `use()` instead of older workarounds. | +| Are you wiring form actions or pending state? | Prefer actions and `useActionState`. | +| Are you about to add memoization for performance? | Stop and justify it; default to compiler-friendly plain code first. | -```typescript -// ✅ ALWAYS: Named imports -import { useState, useEffect, useRef } from "react"; +## Execution Steps -// ❌ NEVER -import React from "react"; -import * as React from "react"; -``` +1. Identify whether the file should stay server-side or become client-side. +2. Remove legacy React imports and manual memoization unless there is a proven exception. +3. Keep render logic direct and compiler-friendly. +4. Use `use()` for supported promise/context reads when it simplifies the flow. +5. Use action-based form patterns for mutation flows when relevant. +6. Pass refs as props in new React 19 component APIs. +7. Validate that the final component model matches the feature's runtime needs. -## Server Components First +## Output Contract -```typescript -// ✅ Server Component (default) - no directive -export default async function Page() { - const data = await fetchData(); - return ; -} +- State whether the component is server or client and why. +- Call out any React 19 modernization applied, such as removing manual memoization, using `use()`, or replacing `forwardRef`. +- Mention whether `nextjs-15` was also required. -// ✅ Client Component - only when needed -"use client"; -export function Interactive() { - const [state, setState] = useState(false); - return ; -} -``` +## References -## When to use "use client" - -- useState, useEffect, useRef, useContext -- Event handlers (onClick, onChange) -- Browser APIs (window, localStorage) - -## use() Hook - -```typescript -import { use } from "react"; - -// Read promises (suspends until resolved) -function Comments({ promise }) { - const comments = use(promise); - return comments.map(c =>
{c.text}
); -} - -// Conditional context (not possible with useContext!) -function Theme({ showTheme }) { - if (showTheme) { - const theme = use(ThemeContext); - return
Themed
; - } - return
Plain
; -} -``` - -## Actions & useActionState - -```typescript -"use server"; -async function submitForm(formData: FormData) { - await saveToDatabase(formData); - revalidatePath("/"); -} - -// With pending state -import { useActionState } from "react"; - -function Form() { - const [state, action, isPending] = useActionState(submitForm, null); - return ( -
- -
- ); -} -``` - -## ref as Prop (No forwardRef) - -```typescript -// ✅ React 19: ref is just a prop -function Input({ ref, ...props }) { - return ; -} - -// ❌ Old way (unnecessary now) -const Input = forwardRef((props, ref) => ); -``` +- [Next.js 15 skill](../nextjs-15/SKILL.md) +- [TypeScript skill](../typescript/SKILL.md) +- [Repository agent rules](../../AGENTS.md) diff --git a/skills/tailwind-4/SKILL.md b/skills/tailwind-4/SKILL.md index 51f57576af..d62ab46732 100644 --- a/skills/tailwind-4/SKILL.md +++ b/skills/tailwind-4/SKILL.md @@ -1,8 +1,6 @@ --- name: tailwind-4 -description: > - Tailwind CSS 4 patterns and best practices. - Trigger: When styling with Tailwind (className, variants, cn()), especially when dynamic styling or CSS variables are involved (no var() in className). +description: "Trigger: When styling with Tailwind CSS 4, especially in `className`, variant composition, `cn()`, or dynamic-value decisions. Enforces Tailwind-first styling rules and escape hatches." license: Apache-2.0 metadata: author: prowler-cloud @@ -12,188 +10,45 @@ metadata: allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task --- -## Styling Decision Tree +## Activation Contract -``` -Tailwind class exists? → className="..." -Dynamic value? → style={{ width: `${x}%` }} -Conditional styles? → cn("base", condition && "variant") -Static only? → className="..." (no cn() needed) -Library can't use class?→ style prop with var() constants -``` +Use this skill when UI styling decisions involve Tailwind class composition, semantic theme usage, or choosing between `className`, `cn()`, and inline styles. -## Critical Rules +## Hard Rules -### Never Use var() in className +- Prefer Tailwind utility classes directly in `className` for static styling. +- Do not put `var(...)` expressions inside `className`; use semantic Tailwind tokens or inline styles where needed. +- Do not use hex colors in class strings; use theme or Tailwind palette classes. +- Use `cn()` only when conditional or merge behavior is real. +- Use inline `style` only for truly dynamic values or third-party APIs that cannot consume class names. -```typescript -// ❌ NEVER: var() in className -
-
+## Decision Gates -// ✅ ALWAYS: Use Tailwind semantic classes -
-
-``` +| Question | Action | +|---|---| +| Static styling only? | Use plain `className="..."`. | +| Conditional or override-prone classes? | Use `cn(...)`. | +| Dynamic numeric or percentage values? | Use the `style` prop. | +| Third-party library prop cannot accept classes? | Pass CSS custom property values or inline style constants. | +| Need a one-off dimension not in the design system? | Use an arbitrary value sparingly, but never for colors. | -### Never Use Hex Colors +## Execution Steps -```typescript -// ❌ NEVER: Hex colors in className -

-

+1. Classify the styling need as static, conditional, dynamic, or third-party-only. +2. Prefer semantic Tailwind utilities and theme tokens first. +3. Introduce `cn()` only if merge logic or conditions justify it. +4. Move dynamic measurements or library-only values into `style` constants. +5. Replace color escape hatches with palette or theme classes. +6. Review the final markup and remove unnecessary wrappers or styling indirection. -// ✅ ALWAYS: Use Tailwind color classes -

-

-``` +## Output Contract -## The cn() Utility +- State which styling path was chosen: plain `className`, `cn()`, or inline `style`. +- Call out any removed anti-pattern such as `var(...)` in `className` or hex colors. +- Mention any remaining escape hatch and why it was necessary. -```typescript -import { clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; +## References -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} -``` - -### When to Use cn() - -```typescript -// ✅ Conditional classes -
- -// ✅ Merging with potential conflicts -