# Contributing to @lumbridgecorp/openclaw-mattermost Thank you for your interest in contributing! This document provides guidelines for setting up your development environment, running tests, and submitting contributions. ## Table of Contents - [Development Setup](#development-setup) - [Running Tests](#running-tests) - [Test Structure](#test-structure) - [Writing Tests](#writing-tests) - [Code Style](#code-style) - [Pull Request Process](#pull-request-process) - [Release Process](#release-process) ## Development Setup ### Prerequisites - **Node.js 22+** (required for native fetch and WebSocket support) - **pnpm** (preferred) or npm/yarn - **Git** - **Mattermost server** (for integration tests) ### Local Setup 1. **Fork the repository** on GitHub 2. **Clone your fork:** ```bash git clone https://github.com/YOUR_USERNAME/openclaw-extentions-mattermost.git cd openclaw-extentions-mattermost ``` 3. **Install dependencies:** ```bash pnpm install # or npm install ``` 4. **Build the project:** ```bash pnpm build # or npm run build ``` 5. **Verify the build:** ```bash pnpm typecheck ``` ## Running Tests ### Quick Test Commands ```bash # Run all tests pnpm test # Run tests in watch mode (for development) pnpm test -- --watch # Run tests with coverage pnpm test -- --coverage # Run a specific test file pnpm test src/mattermost/client.test.ts # Run tests matching a pattern pnpm test -- -t "should create client" # Run tests with verbose output pnpm test -- --verbose ``` ### Test Categories #### Unit Tests Fast, isolated tests that don't require external services: ```bash # Run only unit tests (no integration) pnpm test -- --testPathIgnorePatterns="integration|e2e" ``` Unit tests cover: - Error handling classes - Configuration validation - Utility functions - Retry logic - Security checks #### Integration Tests with Docker Compose Integration tests use Docker Compose to spin up a complete Mattermost instance with PostgreSQL. This provides isolated, reproducible tests against a real Mattermost API. **Setup and Run Integration Tests:** ```bash # Start the test infrastructure docker-compose -f tests/integration/docker-compose.test.yml up -d # Wait for Mattermost to be ready (first startup may take 1-2 minutes) docker-compose -f tests/integration/docker-compose.test.yml ps # Run integration tests pnpm test tests/integration/ # Run specific integration test file pnpm test tests/integration/authentication.test.ts # Run integration tests with verbose output pnpm test tests/integration/ --verbose # Clean up after tests docker-compose -f tests/integration/docker-compose.test.yml down -v ``` **Integration Test Coverage:** | Test File | Coverage | |-----------|----------| | `authentication.test.ts` | Bot token auth, PAT auth, error scenarios | | `send-message.test.ts` | Send to channel/DM, threads, props, error cases | | `edit-message.test.ts` | Edit own messages, props, permission errors | | `delete-message.test.ts` | Delete messages, thread handling, cleanup | | `download-file.test.ts` | File download, security restrictions, MIME validation | | `interactive-buttons.test.ts` | Button rendering, directives, end-to-end flows | **Environment Variables for Integration Tests:** ```bash # Optional: Override defaults (only if not using Docker Compose) export MATTERMOST_URL="http://localhost:8065" export MATTERMOST_SYSADMIN_USER="sysadmin" export MATTERMOST_SYSADMIN_PASSWORD="Sys@dmin123" export MATTERMOST_TEST_USER="testuser" export MATTERMOST_TEST_USER_PASSWORD="Test@user123" export MATTERMOST_BOT_USERNAME="testbot" ``` **Test Data Cleanup:** Integration tests automatically clean up: - Created posts after each test suite - Uploaded files - Test channels and teams (via Docker volume reset) Run `docker-compose -f tests/integration/docker-compose.test.yml down -v` to completely reset the test environment between runs. **Using a Custom Mattermost Instance:** If you prefer to use an existing Mattermost instance for integration tests: ```bash # Set up environment variables export MATTERMOST_URL="https://chat-test.example.com" export MATTERMOST_SYSADMIN_TOKEN="your-sysadmin-token" export MATTERMOST_BOT_TOKEN="your-bot-token" # Run integration tests (skip Docker setup) export SKIP_DOCKER_SETUP=true pnpm test tests/integration/ ``` ⚠️ **Important:** Never run integration tests against production Mattermost instances. Tests create and delete data automatically. #### Client Tests Tests for the Mattermost API client: ```bash pnpm test src/mattermost/client.test.ts pnpm test src/mattermost/client.retry.test.ts ``` #### WebSocket Tests Tests for real-time message monitoring: ```bash pnpm test src/mattermost/monitor-websocket.test.ts ``` ### Test Environment Setup #### Using Environment Variables Create a `.env.test` file (not committed to git): ```bash # Test Mattermost instance MATTERMOST_TEST_URL=https://chat-test.example.com MATTERMOST_TEST_TOKEN=your-test-bot-token MATTERMOST_TEST_PAT=your-test-pat-optional # Test configuration TEST_TIMEOUT=30000 TEST_RETRY_COUNT=3 ``` #### Using Test Fixtures Some tests use mock fixtures instead of real connections: ```typescript // Example: Using mock client import { createMockMattermostClient } from './test-helpers.js'; const mockClient = createMockMattermostClient({ baseUrl: 'https://mock.example.com', botToken: 'mock-token' }); ``` ### Coverage Reports Generate and view coverage: ```bash # Generate coverage report pnpm test -- --coverage # View HTML report open coverage/lcov-report/index.html # Coverage thresholds (enforced in CI) # Statements: 80% # Branches: 75% # Functions: 80% # Lines: 80% ``` ## Test Structure ### Test File Organization ``` extensions/mattermost/src/ ├── mattermost/ │ ├── client.test.ts # Client unit tests │ ├── client.retry.test.ts # Retry logic tests │ ├── monitor-websocket.test.ts # WebSocket tests │ ├── interactions.test.ts # Interactive components tests │ └── ... ├── config-schema.test.ts # Configuration validation ├── security-audit.test.ts # Security audit tests ├── errors.test.ts # Error handling tests └── ... ``` ### Test Naming Conventions - **File naming**: `*.test.ts` for unit tests, `*.integration.test.ts` for integration tests - **Describe blocks**: Use the name of the function/module being tested - **Test names**: Start with "should" and describe the expected behavior Example: ```typescript describe('editMessage', () => { it('should successfully edit a bot message', async () => { // test code }); it('should fail when editing another users message', async () => { // test code }); it('should return PERMISSION_DENIED for system messages', async () => { // test code }); }); ``` ## Writing Tests ### Unit Test Template ```typescript import { describe, it, expect, vi } from 'vitest'; import { editMessage } from './actions.js'; describe('editMessage', () => { it('should edit a message successfully', async () => { // Arrange const mockClient = createMockClient(); const params = { cfg: mockConfig, postId: 'post123', channelId: 'channel123', message: 'Updated' }; // Act const result = await editMessage(params); // Assert expect(result.ok).toBe(true); expect(result.postId).toBe('post123'); }); it('should handle errors gracefully', async () => { // Arrange const params = { /* invalid params */ }; // Act const result = await editMessage(params); // Assert expect(result.ok).toBe(false); expect(result.errorCode).toBe('CONFIG_ERROR'); }); }); ``` ### Testing Error Handling ```typescript import { MattermostError, ErrorCodes } from './errors.js'; describe('error handling', () => { it('should create structured error with context', () => { const error = new MattermostError({ code: ErrorCodes.AUTHENTICATION_FAILED, message: 'Invalid token', context: { operation: 'test', timestamp: new Date().toISOString() } }); expect(error.code).toBe(ErrorCodes.AUTHENTICATION_FAILED); expect(error.retryable).toBe(false); expect(error.toJSON()).toMatchObject({ code: ErrorCodes.AUTHENTICATION_FAILED }); }); }); ``` ### Testing Retries ```typescript import { withRetry } from './errors.js'; describe('withRetry', () => { it('should retry on retryable errors', async () => { let attempts = 0; const operation = async () => { attempts++; if (attempts < 3) { throw new NetworkError('Connection failed', context); } return 'success'; }; const result = await withRetry('test-op', operation, { maxRetries: 3, initialDelayMs: 10 }); expect(result).toBe('success'); expect(attempts).toBe(3); }); }); ``` ### Testing Security Features ```typescript import { collectMattermostSecurityAuditFindings } from './security-audit.js'; describe('security audit', () => { it('should detect hardcoded tokens', async () => { const findings = await collectMattermostSecurityAuditFindings({ cfg: mockConfig, accountId: 'test', account: { ...mockAccount, botToken: 'hardcoded-token-123456789', botTokenSource: 'config' } }); const hardcodedFinding = findings.find( f => f.checkId === 'channels.mattermost.pat.hardcoded_token' ); expect(hardcodedFinding).toBeDefined(); expect(hardcodedFinding?.severity).toBe('critical'); }); }); ``` ### Mocking the Mattermost Client ```typescript import { vi } from 'vitest'; // Create mock client const createMockClient = () => ({ request: vi.fn(), fetchImpl: vi.fn(), baseUrl: 'https://mock.example.com', apiBaseUrl: 'https://mock.example.com/api/v4', token: 'mock-token' }); // Mock module vi.mock('./client.js', () => ({ createMattermostClient: vi.fn(() => createMockClient()) })); ``` ## Code Style ### TypeScript Guidelines - Use strict TypeScript (`strict: true` in tsconfig) - Explicit return types on public functions - Avoid `any` - use `unknown` with type guards - Use `readonly` for immutable properties ### Code Formatting ```bash # Check code style pnpm lint # Fix auto-fixable issues pnpm lint --fix ``` ### Import Order 1. External dependencies (e.g., `openclaw/plugin-sdk`) 2. Internal modules (relative imports) 3. Types only imports Example: ```typescript // 1. External import { z } from 'openclaw/plugin-sdk/zod'; // 2. Internal import { createMattermostClient } from './client.js'; import type { MattermostAccountConfig } from './types.js'; ``` ## Pull Request Process ### Before Submitting 1. **Run all tests:** ```bash pnpm test ``` 2. **Check type safety:** ```bash pnpm typecheck ``` 3. **Run linter:** ```bash pnpm lint ``` 4. **Build project:** ```bash pnpm build ``` 5. **Check test coverage:** ```bash pnpm test -- --coverage ``` ### PR Requirements - **Clear description** of changes - **Link to related issues** - **Tests included** for new features - **Documentation updated** if needed - **No breaking changes** (or clearly marked) - **CI checks passing** ### PR Template ```markdown ## Description Brief description of the change ## Type of Change - [ ] Bug fix - [ ] New feature - [ ] Breaking change - [ ] Documentation update ## Testing - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [ ] All tests passing ## Checklist - [ ] Code follows style guidelines - [ ] Self-review completed - [ ] Documentation updated - [ ] No new warnings ``` ### Review Process 1. Automated checks must pass (CI, coverage) 2. At least one maintainer review required 3. Address review feedback promptly 4. Squash commits before merge (if requested) ## Release Process ### Version Bump ```bash # Update version in package.json npm version patch # or minor, major ``` ### Changelog Update Add entry to `CHANGELOG.md` following Keep a Changelog format: ```markdown ## [1.1.0] - 2026-04-15 ### Added - New feature description ### Fixed - Bug fix description ``` ### Release Steps 1. Update version in `package.json` 2. Update `CHANGELOG.md` 3. Create PR with version bump 4. After merge, tag the release: ```bash git tag -a v1.1.0 -m "Release v1.1.0" git push origin v1.1.0 ``` 5. CI will automatically publish to npm ## Questions? - Open an issue on GitHub - Check existing issues and discussions - Review the [API Reference](docs/API.md) Thank you for contributing!