diff --git a/.cursor/rules/backend-java.mdc b/.cursor/rules/backend-java.mdc new file mode 100644 index 000000000000..6ae981cfde76 --- /dev/null +++ b/.cursor/rules/backend-java.mdc @@ -0,0 +1,143 @@ +--- +description: +globs: app/server/**/*.java +alwaysApply: false +--- +# Java / Spring Boot Backend Conventions + +## Tech Stack + +- Java 25, Spring Boot 3.5, Spring WebFlux (fully reactive), MongoDB, Maven +- Formatting: Palantir Java Format via `mvn spotless:apply` (auto-runs on pre-commit) + +## Project Structure + +Multi-module Maven project under `app/server/`: + +```text +appsmith-server/ — Main server module (controllers, services, repositories, domains) +appsmith-interfaces/ — Shared contracts (models, exceptions, plugin interfaces) +appsmith-plugins/ — 25+ database/API connector plugins +appsmith-git/ — Git integration module +reactive-caching/ — Custom reactive caching framework +``` + +Within `appsmith-server`, packages are organized by feature domain, each with `ce/` and optionally `ce_compatible/` sub-packages. + +## CE-EE Service Pattern (Mandatory for all extensible components) + +### Three-tier inheritance + +```text +Interface: ServiceCE → ServiceCECompatible → Service +Impl: ServiceCEImpl → ServiceCECompatibleImpl → ServiceImpl (@Service) +Package: com.x.services.ce com.x.services.ce_compatible com.x.services +``` + +### Rules + +- `@Service` / `@Component` annotation on CE-Compatible and final `*Impl` classes (not on CE base classes) +- CE impl contains all business logic +- CE-Compatible is an empty pass-through in CE repo (EE overrides it for graceful degradation) +- Final `*Impl` is an empty pass-through in CE repo (EE overrides it for enterprise features) +- Use `protected` methods in CE impl to create override hooks for EE +- This pattern applies to: services, repositories, controllers, solutions, helpers, configurations + +### When to create the CE-Compatible layer + +Always. Even if there's no current EE override, create the empty scaffolding so EE can extend later without modifying CE files. + +## Controller Conventions + +```text +CE class (in controllers/ce/): All endpoint logic, NOT annotated as @RestController +EE class (in controllers/): @RestController + @RequestMapping, empty body, extends CE +``` + +- URL paths: `/api/v1/{resource}` — all paths defined in `UrlCE.java` constants +- Response: always `Mono>` — every response wrapped in `ResponseDTO` +- `@JsonView(Views.Public.class)` on every endpoint method +- Zero business logic in controllers — delegate entirely to services +- No error handling in controllers — handled by global `@ControllerAdvice` (`GlobalExceptionHandler`) + +## Repository Conventions + +Dual hierarchy: +- `BaseRepository` (extends `ReactiveMongoRepository`) — standard CRUD +- `AppsmithRepository` (custom) — ACL-aware queries via `queryBuilder()` fluent API + +```java +queryBuilder() + .criteria(Bridge.equal(Application.Fields.workspaceId, workspaceId)) + .permission(aclPermission) + .all(); +``` + +- No `@Query` annotations — use the `Bridge` query builder exclusively +- Field references via Lombok `@FieldNameConstants` (`Fields` inner class) +- Soft deletes: `archive()` sets `deletedAt`; queries auto-filter deleted records + +## Domain / DTO Conventions + +Hierarchy: `BaseDomain` (id, policyMap, timestamps) → `GitSyncedDomain` → `RefAwareDomain` + +- `@Getter`, `@Setter`, `@ToString` on domains — **not** `@Data` (avoids generated equals/hashCode) +- `@FieldNameConstants` on all domains/DTOs for type-safe field references +- `@JsonView` annotations control API visibility (`Views.Public.class`, `Views.Internal.class`, `Git.class`, `FromRequest.class`) +- `@Transient` for computed fields not persisted to MongoDB +- Domains/DTOs also follow CE-EE split: `ActionDTO extends ActionCE_DTO` + +## Dependency Injection + +- Constructor-based injection exclusively (no `@Autowired` on fields) +- Use `@RequiredArgsConstructor` or `@AllArgsConstructor` (Lombok) +- `@Lazy` to break circular dependencies +- `@Autowired(required = false)` for optional dependencies + +## Reactive Patterns + +All service methods return `Mono` or `Flux`. Key patterns: + +- `.switchIfEmpty(Mono.error(new AppsmithException(...)))` — standard "not found" +- `Mono.cache()` — prevent duplicate subscriptions when a Mono feeds multiple operators +- `Mono.zip()` / `.zipWith()` / `.zipWhen()` — combine parallel or dependent operations +- `Mono.defer()` — lazy evaluation inside `.switchIfEmpty()` and `.then()` +- `.flatMap()` chains — primary mechanism for sequential reactive operations +- `.onErrorResume()` — graceful degradation +- `.onErrorMap()` — translate errors to `AppsmithException` +- Never block reactive streams — no `.block()` in production code + +## Error Handling + +- `AppsmithException` wraps `AppsmithError` enum (100+ entries with HTTP status, error code `AE-{DOMAIN}-{NUMBER}`, message template) +- Throw via `new AppsmithException(AppsmithError.SOME_ERROR, args...)` +- Global handler (`GlobalExceptionHandler`) catches and returns `ResponseDTO` +- Plugin errors: `AppsmithPluginException` wraps `BasePluginError` + +## Feature Flags + +- `@FeatureFlagged(featureFlagName = FeatureFlagEnum.XXX)` on EE methods +- Spring AOP `@Around` advice dispatches: flag ON → EE method; flag OFF → superclass (CE-Compatible/CE) +- For reactive methods: aspect uses `flatMap` on the flag check Mono +- For synchronous methods: requires `organizationId` parameter; reads from in-memory org cache +- New flags: add to `FeatureFlagEnum` in `appsmith-interfaces` +- Flag naming: `release_*` (rollout), `license_*` (paid), `rollout_*` (gradual), `ab_*` (experiment) + +## Custom Annotations + +| Annotation | Purpose | +|------------|---------| +| `@FeatureFlagged` | Controls method execution via feature flag + AOP | +| `@Encrypted` | Marks fields for at-rest encryption | +| `@Cache` / `@CacheEvict` | Reactive method result caching / eviction | +| `@DistributedLock` | Distributed lock with TTL (default 5 min) | +| `@RateLimit` | Rate-limits API endpoints | + +## Build Commands + +```bash +cd app/server && mvn spotless:apply # Format code +cd app/server && mvn spotless:check # Check formatting only +cd app/server && ./build.sh -DskipTests # Build without tests +cd app/server && ./build.sh -DskipTests -T 8 # Build without tests (8 threads) +``` diff --git a/.cursor/rules/bug-fix-verification.mdc b/.cursor/rules/bug-fix-verification.mdc new file mode 100644 index 000000000000..06ca61394b8c --- /dev/null +++ b/.cursor/rules/bug-fix-verification.mdc @@ -0,0 +1,57 @@ +--- +description: Checklist for fixing bugs, debugging issues, and verifying bug fixes +globs: +alwaysApply: false +--- +# Bug Fix Workflow (TDD Approach) + +## 1. Understand + +- Reproduce the issue locally and understand the bug report +- Identify root cause through code exploration — fix the cause, not symptoms +- Check if there's a related issue/ticket to reference + +## 2. Write Failing Test(s) + +Write test(s) that demonstrate the bug before writing any fix code. +- Server: add test in the relevant `*Test.java` class +- Client: add test in the relevant `*.test.ts(x)` file +- Test both CE and EE paths if the bug touches feature-flagged code + +## 3. Verify RED + +Run the test(s) and confirm they **FAIL**: +```bash +# Server +cd app/server && source envs/test.env.example && mvn test -pl appsmith-server -Dtest=ClassName + +# Client +cd app/client && yarn test:jest -- --testPathPattern= +``` + +If the test passes without any fix, it does not cover the bug. Rewrite the test. + +## 4. Implement Fix + +- Address the root cause with minimal, focused changes +- Follow the CE-EE pattern if the fix touches extensible components +- No hardcoded values, no debug logging (`console.log`, `System.out.println`) +- Use safe property access (optional chaining or lodash/get for nested objects) +- Handle edge cases and null/undefined gracefully + +## 5. Verify GREEN + +Run the same test(s) and confirm they now **PASS**. Use the same commands from step 3. + +## 6. Sanity Check + +Run closely related tests to catch obvious regressions. Do NOT run the full test suite locally — that's what CI is for. +- Find test classes in the same package/module as the changed code +- Run those specific tests to verify nothing adjacent broke +- Full regression is validated by CI after push + +## Before Committing + +- Formatting handled automatically by Husky pre-commit hooks +- PR title: `fix(scope): description` +- Reference the issue/ticket in PR description diff --git a/.cursor/rules/client-unit-tests.mdc b/.cursor/rules/client-unit-tests.mdc new file mode 100644 index 000000000000..901230788dde --- /dev/null +++ b/.cursor/rules/client-unit-tests.mdc @@ -0,0 +1,149 @@ +--- +description: +globs: app/client/src/**/*.test.ts,app/client/src/**/*.test.tsx +alwaysApply: false +--- +# Client Unit Test Conventions (Jest) + +## Framework + +- Jest 29 + React Testing Library (no Enzyme, no snapshot testing) +- `@testing-library/react` for rendering/querying +- `@testing-library/react` also provides `renderHook` for hook testing +- `@testing-library/user-event` for user interaction simulation +- `@testing-library/jest-dom` for DOM matchers +- MSW (Mock Service Worker) for API mocking +- `redux-mock-store` for Redux store mocking +- Test timeout: 9000ms + +## Running Tests + +```bash +cd app/client && yarn test:unit # All unit tests with coverage +cd app/client && yarn test:jest -- --testPathPattern= # Specific tests (watch mode) +``` + +## Test Setup (Automatic) + +Configured in `test/setup.ts`: +- MSW server starts `beforeAll`, resets handlers `afterEach`, closes `afterAll` +- Polyfills: `crypto`, `TextDecoder`, `ReadableStream`, `structuredClone` +- Mocks: `IntersectionObserver`, `ResizeObserver`, `scrollTo` + +## Test Patterns + +### Component test (with Redux) + +```typescript +import { render, screen } from "test/testUtils"; + +describe("MyComponent", () => { + it("renders correctly with given props", () => { + render(, { + initialState: store.getState(), + url: "/app/page-123", + }); + + expect(screen.getByText("test")).toBeInTheDocument(); + }); +}); +``` + +The custom `render` from `test/testUtils` wraps components in ``, ``, and ``. + +### Component test (simple, no Redux) + +```typescript +import { render, screen } from "@testing-library/react"; + +it("renders label", () => { + render(