diff --git a/.aiassistant/rules/Creating and maintaining tests.md b/.aiassistant/rules/Creating and maintaining tests.md new file mode 100644 index 00000000..dddc0208 --- /dev/null +++ b/.aiassistant/rules/Creating and maintaining tests.md @@ -0,0 +1,286 @@ +--- +apply: always +--- + +# Guidelines for Creating and Maintaining Tests + +## Testing Framework + +This project uses **WebdriverIO (wdio)** as the primary testing framework with Vite service integration. Tests support both desktop and mobile environments (including Appium for Android/iOS). + +## Running Tests + +```bash +# Run full test suite +npm run wdio + +# Run tests excluding Safari +npm run test:mobile + +# Run Android-targeted specs +npm run test:android +``` + +## Test File Location and Naming + +- **Location**: All test files should be placed in `test/specs/` +- **Naming Convention**: Use kebab-case with `.spec.js` suffix + - ✅ `i18n-settings-wheel.spec.js` + - ✅ `user-authentication.spec.js` + - ❌ `UserAuth.test.js` + +## Test Structure + +### Basic Template + +```javascript +/** + * Brief description of what this test suite covers + */ +describe('Feature or Component Name', () => { + it('should describe expected behavior', async () => { + await browser.url('/'); + // Test implementation + expect(result).toBe(expectedValue); + }); +}); +``` + +### Key Conventions + +1. **Use descriptive test names**: Start with `should` to describe expected behavior +2. **Always use `async/await`**: All browser interactions are asynchronous +3. **Include JSDoc comments**: Add a comment block at the top describing the test purpose +4. **Group related tests**: Use `describe` blocks to organize related test cases + +## Writing Tests + +### Navigation + +```javascript +// Navigate to a page +await browser.url('/'); +await browser.url('/dashboard'); +``` + +### Browser Interactions + +```javascript +// Get page title +const title = await browser.getTitle(); + +// Execute JavaScript in browser context +const result = await browser.executeAsync(async (args, done) => { + try { + // Your async code here + done({ success: true, data: result }); + } catch (e) { + done({ success: false, error: e.message }); + } +}, arguments); +``` + +### Assertions + +```javascript +// Use Jest-style expect assertions +expect(title).toContain('Expected Text'); +expect(result.success).toBe(true); +expect(items.length).toBeGreaterThan(0); +``` + +## Best Practices + +### DO: +- ✅ Test one behavior per `it` block +- ✅ Use meaningful variable names +- ✅ Add console.log for debugging complex assertions +- ✅ Handle errors gracefully in `executeAsync` callbacks +- ✅ Clean up test state when necessary + +### DON'T: +- ❌ Write tests that depend on execution order +- ❌ Hardcode sensitive data (use environment variables) +- ❌ Skip error handling in async operations +- ❌ Leave commented-out code in test files + +## Testing i18n/Translations + +When testing internationalization: + +1. Define expected translation keys as an array +2. Fetch locale files via browser context +3. Verify all required keys exist in each locale +4. Check parity between locales (same keys in all languages) + +```javascript +const requiredKeys = ['key1', 'key2', 'key3']; + +it('should have all translations in English', async () => { + await browser.url('/'); + const result = await browser.executeAsync(async (keys, done) => { + const response = await fetch('/src/i18n/locales/en.json'); + const data = await response.json(); + // Verify keys exist + done({ success: true }); + }, requiredKeys); + expect(result.success).toBe(true); +}); +``` + +## Mobile Testing Prerequisites + +For mobile tests (`npm run test:mobile` or `npm run test:android`): +- Android SDK and emulator must be installed +- Appium drivers configured +- For iOS: Xcode and iOS tooling required + +## Maintaining Tests + +1. **Keep tests updated**: When modifying features, update corresponding tests +2. **Review failing tests**: Investigate failures before marking as skipped +3. **Remove obsolete tests**: Delete tests for removed features +4. **Refactor shared logic**: Extract common test utilities to helper files in `test/` +--- +apply: always +--- + +# Guidelines for Creating and Maintaining Tests + +## Testing Framework + +This project uses **WebdriverIO (wdio)** as the primary testing framework with Vite service integration. Tests support both desktop and mobile environments (including Appium for Android/iOS). + +## Running Tests + +```bash +# Run full test suite +npm run wdio + +# Run tests excluding Safari +npm run test:mobile + +# Run Android-targeted specs +npm run test:android +``` + +## Test File Location and Naming + +- **Location**: All test files should be placed in `test/specs/` +- **Naming Convention**: Use kebab-case with `.spec.js` suffix + - ✅ `i18n-settings-wheel.spec.js` + - ✅ `user-authentication.spec.js` + - ❌ `UserAuth.test.js` + +## Test Structure + +### Basic Template + +```javascript +/** + * Brief description of what this test suite covers + */ +describe('Feature or Component Name', () => { + it('should describe expected behavior', async () => { + await browser.url('/'); + // Test implementation + expect(result).toBe(expectedValue); + }); +}); +``` + +### Key Conventions + +1. **Use descriptive test names**: Start with `should` to describe expected behavior +2. **Always use `async/await`**: All browser interactions are asynchronous +3. **Include JSDoc comments**: Add a comment block at the top describing the test purpose +4. **Group related tests**: Use `describe` blocks to organize related test cases + +## Writing Tests + +### Navigation + +```javascript +// Navigate to a page +await browser.url('/'); +await browser.url('/dashboard'); +``` + +### Browser Interactions + +```javascript +// Get page title +const title = await browser.getTitle(); + +// Execute JavaScript in browser context +const result = await browser.executeAsync(async (args, done) => { + try { + // Your async code here + done({ success: true, data: result }); + } catch (e) { + done({ success: false, error: e.message }); + } +}, arguments); +``` + +### Assertions + +```javascript +// Use Jest-style expect assertions +expect(title).toContain('Expected Text'); +expect(result.success).toBe(true); +expect(items.length).toBeGreaterThan(0); +``` + +## Best Practices + +### DO: +- ✅ Test one behavior per `it` block +- ✅ Use meaningful variable names +- ✅ Add console.log for debugging complex assertions +- ✅ Handle errors gracefully in `executeAsync` callbacks +- ✅ Clean up test state when necessary + +### DON'T: +- ❌ Write tests that depend on execution order +- ❌ Hardcode sensitive data (use environment variables) +- ❌ Skip error handling in async operations +- ❌ Leave commented-out code in test files + +## Testing i18n/Translations + +When testing internationalization: + +1. Define expected translation keys as an array +2. Fetch locale files via browser context +3. Verify all required keys exist in each locale +4. Check parity between locales (same keys in all languages) + +```javascript +const requiredKeys = ['key1', 'key2', 'key3']; + +it('should have all translations in English', async () => { + await browser.url('/'); + const result = await browser.executeAsync(async (keys, done) => { + const response = await fetch('/src/i18n/locales/en.json'); + const data = await response.json(); + // Verify keys exist + done({ success: true }); + }, requiredKeys); + expect(result.success).toBe(true); +}); +``` + +## Mobile Testing Prerequisites + +For mobile tests (`npm run test:mobile` or `npm run test:android`): +- Android SDK and emulator must be installed +- Appium drivers configured +- For iOS: Xcode and iOS tooling required + +## Maintaining Tests + +1. **Keep tests updated**: When modifying features, update corresponding tests +2. **Review failing tests**: Investigate failures before marking as skipped +3. **Remove obsolete tests**: Delete tests for removed features +4. **Refactor shared logic**: Extract common test utilities to helper files in `test/` diff --git a/openapi.yaml b/openapi.yaml index 1b1f13fb..04e7ae15 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2756,6 +2756,11 @@ paths: order_priority: type: integer default: 0 + services: + type: array + description: Optional services enabled by this task. Items must be valid service enum names. + items: + $ref: '#/components/schemas/SelfserveLaneService' responses: '200': description: Successfully added task @@ -2802,6 +2807,12 @@ paths: type: string order_priority: type: integer + services: + type: array + nullable: true + description: Services enabled by this task. Set to null to clear all services. + items: + $ref: '#/components/schemas/SelfserveLaneService' responses: '200': description: Successfully updated task @@ -5060,6 +5071,101 @@ paths: schema: $ref: '#/components/schemas/SelfServeLaneStatus' + /modules/self-serve/lane/services/allowed: + post: + tags: + - Modules + summary: Set allowed services for a lane based on shown tasks + description: | + Updates the set of services that are allowed to be manually activated for a given self-serve lane, + derived from the tasks currently shown to the user after answering the self-serve questions. + This endpoint does not activate anything by itself; it only sets what is allowed to be activated. + operationId: setSelfServeLaneAllowedServices + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - lane_id + properties: + lane_id: + type: integer + task_ids: + type: array + description: List of task IDs that are currently shown to the user + items: + type: integer + responses: + '200': + description: Allowed services updated + content: + application/json: + schema: + type: object + properties: + lane_id: + type: integer + allowed_services: + type: array + items: + type: string + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /modules/self-serve/lane/relay/machine/enable: + post: + tags: + - Modules + summary: Manually enable MACHINE relay for a lane + description: | + Manually turns on the MACHINE relay for a self-serve lane if and only if the current allowed services + include `MACHINE` (set via `/modules/self-serve/lane/services/allowed`). The relay is never automatically + enabled; an explicit call to this endpoint is required. + operationId: enableSelfServeLaneMachineRelay + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - lane_id + properties: + lane_id: + type: integer + duration: + type: integer + description: Optional number of seconds after which the relay should automatically turn off + responses: + '200': + description: MACHINE relay enabled + content: + application/json: + schema: + type: object + properties: + lane_id: + type: integer + relay: + type: string + enabled: + type: boolean + duration: + type: integer + nullable: true + '401': + $ref: '#/components/responses/Unauthorized' + '403': + description: Not allowed to enable MACHINE relay (no matching task currently shown) + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + # Module - Other Integration Endpoints /modules/motorapi/lookup: get: @@ -6609,6 +6715,12 @@ components: type: integer description: The product ID used for minute-based billing + SelfserveLaneService: + type: string + description: Allowed self-serve lane service name + enum: + - MACHINE + DepartmentSelfserveQuestion: type: object properties: @@ -6672,6 +6784,12 @@ components: order_priority: type: integer description: Display order priority (lower numbers shown first) + services: + type: array + description: Services enabled by this task. Each item must be a valid service enum name. + items: + $ref: '#/components/schemas/SelfserveLaneService' + default: [] created_at: type: string format: date-time diff --git a/src/components/displays/department/tables/SelfServeTasksTable.vue b/src/components/displays/department/tables/SelfServeTasksTable.vue index 173e0e68..0c7736a9 100644 --- a/src/components/displays/department/tables/SelfServeTasksTable.vue +++ b/src/components/displays/department/tables/SelfServeTasksTable.vue @@ -98,6 +98,7 @@ const onDrop = async (event, newIndex) => {