Archon/archon-ui-main/test/config/api.test.ts
Wirasm 86dd1b0749
Improve development environment with Docker Compose profiles (#435)
* Add improved development environment with backend in Docker and frontend locally

- Created dev.bat script to run backend services in Docker and frontend locally
- Added docker-compose.backend.yml for backend-only Docker setup
- Updated package.json to run frontend on port 3737
- Fixed api.ts to use default port 8181 instead of throwing error
- Script automatically stops production containers to avoid port conflicts
- Provides instant HMR for frontend development

* Refactor development environment setup: replace dev.bat with Makefile for cross-platform support and enhanced commands

* Enhance development environment: add environment variable checks and update test commands for frontend and backend

* Improve development environment with Docker Compose profiles

This commit enhances the development workflow by replacing the separate
docker-compose.backend.yml file with Docker Compose profiles, fixing
critical service discovery issues, and adding comprehensive developer
tooling through an improved Makefile system.

Key improvements:
- Replace docker-compose.backend.yml with cleaner profile approach
- Fix service discovery by maintaining consistent container names
- Fix port mappings (3737:3737 instead of 3737:5173)
- Add make doctor for environment validation
- Fix port configuration and frontend HMR
- Improve error handling with .SHELLFLAGS in Makefile
- Add comprehensive port configuration via environment variables
- Simplify make dev-local to only run essential services
- Add logging directory creation for local development
- Document profile strategy in docker-compose.yml

These changes provide three flexible development modes:
- Hybrid mode (default): Backend in Docker, frontend local with HMR
- Docker mode: Everything in Docker for production-like testing
- Local mode: API server and UI run locally

Co-authored-by: Zak Stam <zaksnet@users.noreply.github.com>

* Fix make stop command to properly handle Docker Compose profiles

The stop command now explicitly specifies all profiles to ensure
all containers are stopped regardless of how they were started.

* Fix README to document correct make commands

- Changed 'make lint' to 'make lint-frontend' and 'make lint-backend'
- Removed non-existent 'make logs-server' command
- Added 'make watch-mcp' and 'make watch-agents' commands
- All documented make commands now match what's available in Makefile

* fix: Address critical issues from code review #435

- Create robust environment validation script (check-env.js) that properly parses .env files
- Fix Docker healthcheck port mismatch (5173 -> 3737)
- Remove hard-coded port flags from package.json to allow environment configuration
- Fix Docker detection logic using /.dockerenv instead of HOSTNAME
- Normalize container names to lowercase (archon-server, archon-mcp, etc.)
- Improve stop-local command with port-based fallback for process killing
- Fix API configuration fallback chain to include VITE_PORT
- Fix Makefile shell variable expansion using runtime evaluation
- Update .PHONY targets with comprehensive list
- Add --profile flags to Docker Compose commands in README
- Add VITE_ARCHON_SERVER_PORT to docker-compose.yml
- Add Node.js 18+ to prerequisites
- Use dynamic ports in Makefile help messages
- Add lint alias combining frontend and backend linting
- Update .env.example documentation
- Scope .gitignore logs entry to /logs/

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix container name resolution for MCP server

- Add dynamic container name resolution with three-tier strategy
- Support environment variables for custom container names
- Add service discovery labels to docker-compose services
- Update BackendStartupError with correct container name references

* Fix frontend test failures in API configuration tests

- Update environment variable names to use VITE_ prefix that matches production code
- Fix MCP client service tests to use singleton instance export
- Update default behavior tests to expect fallback to port 8181
- All 77 frontend tests now pass

* Fix make stop-local to avoid Docker daemon interference

Replace aggressive kill -9 with targeted process termination:
- Filter processes by command name (node/vite/python/uvicorn) before killing
- Use graceful SIGTERM instead of SIGKILL
- Add process verification to avoid killing Docker-related processes
- Improve logging with descriptive step messages

* refactor: Simplify development workflow based on comprehensive review

- Reduced Makefile from 344 lines (43 targets) to 83 lines (8 essential targets)
- Removed unnecessary environment variables (*_CONTAINER_NAME variables)
- Fixed Windows compatibility by removing Unix-specific commands
- Added security fixes to check-env.js (path validation)
- Simplified MCP container discovery to use fixed container names
- Fixed 'make stop' to properly handle Docker Compose profiles
- Updated documentation to reflect simplified workflow
- Restored original .env.example with comprehensive Supabase key documentation

This addresses all critical issues from code review:
- Cross-platform compatibility 
- Security vulnerabilities fixed 
- 81% reduction in complexity 
- Maintains all essential functionality 

All tests pass: Frontend (77/77), Backend (267/267)

* feat: Add granular test and lint commands to Makefile

- Split test command into test-fe and test-be for targeted testing
- Split lint command into lint-fe and lint-be for targeted linting
- Keep original test and lint commands that run both
- Update help text with new commands for better developer experience

* feat: Improve Docker Compose detection and prefer modern syntax

- Prefer 'docker compose' (plugin) over 'docker-compose' (standalone)
- Add better error handling in Makefile with proper exit on failures
- Add Node.js check before running environment scripts
- Pass environment variables correctly to frontend in hybrid mode
- Update all documentation to use modern 'docker compose' syntax
- Auto-detect which Docker Compose version is available

* docs: Update CONTRIBUTING.md to reflect simplified development workflow

- Add Node.js 18+ as prerequisite for hybrid development
- Mark Make as optional throughout the documentation
- Update all docker-compose commands to modern 'docker compose' syntax
- Add Make command alternatives for testing (make test, test-fe, test-be)
- Document make dev for hybrid development mode
- Remove linting requirements until codebase errors are resolved

* fix: Rename frontend service to archon-frontend for consistency

Aligns frontend service naming with other services (archon-server, archon-mcp, archon-agents) for better consistency in Docker image naming patterns.

---------

Co-authored-by: Zak Stam <zakscomputers@hotmail.com>
Co-authored-by: Zak Stam <zaksnet@users.noreply.github.com>
2025-08-22 17:18:10 +03:00

236 lines
7.4 KiB
TypeScript

/**
* Tests for API configuration port requirements
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
describe('API Configuration', () => {
let originalEnv: any;
beforeEach(() => {
// Save original environment
originalEnv = { ...import.meta.env };
// Clear the module cache to ensure fresh imports
vi.resetModules();
});
afterEach(() => {
// Restore original environment
Object.keys(import.meta.env).forEach(key => {
delete (import.meta.env as any)[key];
});
Object.assign(import.meta.env, originalEnv);
});
describe('getApiUrl', () => {
it('should use VITE_API_URL when provided', async () => {
// Set VITE_API_URL
(import.meta.env as any).VITE_API_URL = 'http://custom-api:9999';
const { getApiUrl } = await import('../../src/config/api');
expect(getApiUrl()).toBe('http://custom-api:9999');
});
it('should return empty string in production mode', async () => {
// Set production mode
(import.meta.env as any).PROD = true;
delete (import.meta.env as any).VITE_API_URL;
const { getApiUrl } = await import('../../src/config/api');
expect(getApiUrl()).toBe('');
});
it('should use default port 8181 when no port environment variables are set in development', async () => {
// Development mode without any port variables
delete (import.meta.env as any).PROD;
delete (import.meta.env as any).VITE_API_URL;
delete (import.meta.env as any).VITE_ARCHON_SERVER_PORT;
delete (import.meta.env as any).VITE_PORT;
delete (import.meta.env as any).ARCHON_SERVER_PORT;
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: 'localhost'
},
writable: true
});
const { getApiUrl } = await import('../../src/config/api');
expect(getApiUrl()).toBe('http://localhost:8181');
});
it('should use VITE_ARCHON_SERVER_PORT when set in development', async () => {
// Development mode with custom port via VITE_ prefix
delete (import.meta.env as any).PROD;
delete (import.meta.env as any).VITE_API_URL;
(import.meta.env as any).VITE_ARCHON_SERVER_PORT = '9191';
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: 'localhost'
},
writable: true
});
const { getApiUrl } = await import('../../src/config/api');
expect(getApiUrl()).toBe('http://localhost:9191');
});
it('should use custom port with https protocol', async () => {
// Development mode with custom port and https via VITE_ prefix
delete (import.meta.env as any).PROD;
delete (import.meta.env as any).VITE_API_URL;
(import.meta.env as any).VITE_ARCHON_SERVER_PORT = '8443';
// Mock window.location with https
Object.defineProperty(window, 'location', {
value: {
protocol: 'https:',
hostname: 'example.com'
},
writable: true
});
const { getApiUrl } = await import('../../src/config/api');
expect(getApiUrl()).toBe('https://example.com:8443');
});
});
describe('getWebSocketUrl', () => {
it('should convert http to ws', async () => {
(import.meta.env as any).VITE_API_URL = 'http://localhost:8181';
const { getWebSocketUrl } = await import('../../src/config/api');
expect(getWebSocketUrl()).toBe('ws://localhost:8181');
});
it('should convert https to wss', async () => {
(import.meta.env as any).VITE_API_URL = 'https://secure.example.com:8443';
const { getWebSocketUrl } = await import('../../src/config/api');
expect(getWebSocketUrl()).toBe('wss://secure.example.com:8443');
});
it('should handle production mode with https', async () => {
(import.meta.env as any).PROD = true;
delete (import.meta.env as any).VITE_API_URL;
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
protocol: 'https:',
host: 'app.example.com'
},
writable: true
});
const { getWebSocketUrl } = await import('../../src/config/api');
expect(getWebSocketUrl()).toBe('wss://app.example.com');
});
});
describe('Port validation', () => {
it('should handle various port formats', async () => {
const testCases = [
{ port: '80', expected: 'http://localhost:80' },
{ port: '443', expected: 'http://localhost:443' },
{ port: '3000', expected: 'http://localhost:3000' },
{ port: '8080', expected: 'http://localhost:8080' },
{ port: '65535', expected: 'http://localhost:65535' },
];
for (const { port, expected } of testCases) {
vi.resetModules();
delete (import.meta.env as any).PROD;
delete (import.meta.env as any).VITE_API_URL;
(import.meta.env as any).VITE_ARCHON_SERVER_PORT = port;
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: 'localhost'
},
writable: true
});
const { getApiUrl } = await import('../../src/config/api');
expect(getApiUrl()).toBe(expected);
}
});
});
});
describe('MCP Client Service Configuration', () => {
let originalEnv: any;
beforeEach(() => {
originalEnv = { ...import.meta.env };
vi.resetModules();
});
afterEach(() => {
Object.keys(import.meta.env).forEach(key => {
delete (import.meta.env as any)[key];
});
Object.assign(import.meta.env, originalEnv);
});
it('should throw error when ARCHON_MCP_PORT is not set', async () => {
delete (import.meta.env as any).ARCHON_MCP_PORT;
const { mcpClientService } = await import('../../src/services/mcpClientService');
await expect(mcpClientService.createArchonClient()).rejects.toThrow('ARCHON_MCP_PORT environment variable is required');
await expect(mcpClientService.createArchonClient()).rejects.toThrow('Default value: 8051');
});
it('should use ARCHON_MCP_PORT when set', async () => {
(import.meta.env as any).ARCHON_MCP_PORT = '9051';
(import.meta.env as any).ARCHON_SERVER_PORT = '8181';
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
protocol: 'http:',
hostname: 'localhost'
},
writable: true
});
// Mock the API call
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
id: 'test-id',
name: 'Archon',
transport_type: 'http',
connection_status: 'connected'
})
});
const { mcpClientService } = await import('../../src/services/mcpClientService');
try {
await mcpClientService.createArchonClient();
// Verify the fetch was called with the correct URL
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/api/mcp/clients'),
expect.objectContaining({
method: 'POST',
body: expect.stringContaining('9051')
})
);
} catch (error) {
// If it fails due to actual API call, that's okay for this test
// We're mainly testing that it constructs the URL correctly
expect(error).toBeDefined();
}
});
});