Files
openclaw-mattermost-extension/CONTRIBUTING.md
T

12 KiB

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

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:

    git clone https://github.com/YOUR_USERNAME/openclaw-extentions-mattermost.git
    cd openclaw-extentions-mattermost
    
  3. Install dependencies:

    pnpm install
    # or
    npm install
    
  4. Build the project:

    pnpm build
    # or
    npm run build
    
  5. Verify the build:

    pnpm typecheck
    

Running Tests

Quick Test Commands

# 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:

# 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:

# 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:

# 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:

# 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:

pnpm test src/mattermost/client.test.ts
pnpm test src/mattermost/client.retry.test.ts

WebSocket Tests

Tests for real-time message monitoring:

pnpm test src/mattermost/monitor-websocket.test.ts

Test Environment Setup

Using Environment Variables

Create a .env.test file (not committed to git):

# 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:

// 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:

# 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:

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

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

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

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

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

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

# 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:

// 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:

    pnpm test
    
  2. Check type safety:

    pnpm typecheck
    
  3. Run linter:

    pnpm lint
    
  4. Build project:

    pnpm build
    
  5. Check test coverage:

    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

## 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

# Update version in package.json
npm version patch  # or minor, major

Changelog Update

Add entry to CHANGELOG.md following Keep a Changelog format:

## [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:
    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

Thank you for contributing!