♻️ Refactor ApiService into functional decomposition#127
Conversation
Extract API logic from monolithic class into functional modules: - src/api/core.js: Pure functions (headers, SHA, parsing) - src/api/client.js: Client factory with token refresh - src/api/endpoints.js: API endpoint functions - src/api/index.js: Public exports Key improvements: - 55 new tests with ZERO vi.mock - pure input/output assertions - Dependency injection pattern for testability - ApiService class preserved as thin wrapper for backwards compat Migrated internal consumers to use functional API: - server-manager.js, test-runner.js, uploader.js, tdd-service.js - api-handler.js now accepts dependencies for testing Net reduction: -1114 lines (removed mock-heavy tests)
Code ReviewThis is an excellent refactoring that successfully decomposes the monolithic ✅ StrengthsArchitecture & Design
Code Quality
Testing
🔍 Issues Found1. CRITICAL: Mutable export in client.jsLocation: export let DEFAULT_API_URL = 'https://app.vizzly.dev';Issue: Using Recommendation: export const DEFAULT_API_URL = 'https://app.vizzly.dev';2. Minor: Inconsistent null handlingLocation: export function buildScreenshotCheckObject(sha256, name, metadata = {}) {
let meta = metadata || {};Issue: The parameter already defaults to Recommendation: Either remove the redundant check or document that // Option 1: Trust the default
export function buildScreenshotCheckObject(sha256, name, metadata = {}) {
return {
sha256,
name,
browser: metadata.browser || 'chrome',
// ...
// Option 2: Document null handling
/**
* @param {Object|null} metadata - Screenshot metadata (null treated as {})
*/
export function buildScreenshotCheckObject(sha256, name, metadata = {}) {
let meta = metadata || {};3. Minor: Console.debug left in production codeLocation: console.debug(
'SHA check failed, continuing without deduplication:',Issue: While using Recommendation: Use the output utility: import * as output from '../utils/output.js';
// ...
} catch (error) {
output.debug('SHA check failed, continuing without deduplication:', error.message);4. Minor: Empty catch block swallows errorsLocation: } catch {
return null;
}Issue: The token refresh silently swallows all errors, making debugging difficult. While this is intentional for graceful degradation, it would be helpful to log at debug level. Recommendation: } catch (error) {
// Token refresh failed, fall back to normal auth error handling
// Don't log at error level as this is expected when offline/refresh token expired
return null;
}Or use the output system if available in this scope. 💡 Suggestions1. Consider TypeScript or JSDoc types for better IDE supportThe JSDoc is excellent, but consider using more specific types: /**
* @typedef {Object} ApiClient
* @property {(endpoint: string, options?: Object, isRetry?: boolean) => Promise<Object>} request
* @property {() => string} getBaseUrl
* @property {() => string|null} getToken
* @property {() => string} getUserAgent
*/2. Export error classes from index.jsFor consistency, consider re-exporting error parsing utilities from 3. Add integration testsThe PR mentions "Integration tests for actual HTTP calls are in tests/api/integration/" but I don't see those files in the diff. Consider adding a few smoke tests against a test server or using MSW (Mock Service Worker) for HTTP-level integration testing. 🔒 Security Review✅ No security issues found
⚡ Performance Considerations✅ Good performance practices
Minor suggestion: Consider streaming for very large screenshots instead of loading entire buffers, though this is likely not a practical concern for screenshot sizes. 📊 Test CoverageThe PR claims all 936 tests pass with a net reduction of 1114 lines. This is impressive - reducing code while improving testability is the hallmark of good refactoring. Recommendation: Ensure CI is green before merging. The test coverage thresholds (75% lines/functions, 70% branches per CLAUDE.md) should be maintained. ✅ Alignment with CLAUDE.md
Final VerdictAPPROVE with minor fixes This refactoring is well-executed and ready to merge after addressing the critical issue: Required before merge:
Recommended (non-blocking):
Excellent work on this refactoring! The functional decomposition significantly improves code quality and maintainability. 🎉 |
- Change DEFAULT_API_URL from let to const - Replace console.debug with output.debug utility
## Summary - Extract pure functions from `ConfigService` into `src/config/core.js` (deepMerge, serialization, validation, config building) - Create `src/config/operations.js` with dependency-injected I/O operations (getConfig, updateConfig, etc.) - Add 91 new tests using simple input/output assertions instead of vi.mock - Keep `ConfigService` class as thin backwards-compatible wrapper, marked `@deprecated` This follows the established pattern from #125 (TddService), #127 (ApiService), and #129 (auth-service). ## Test plan - [x] All 1078 existing tests pass - [x] 91 new tests for config module (57 core + 34 operations) - [x] No `vi.mock` in new tests - [x] Build succeeds - [x] Lint passes
Summary
Extracts API logic from monolithic
ApiServiceclass into functional modules for better testability and maintainability.New Architecture
Key Changes
vi.mock- pure input/output assertions and dependency injectiongetBuild(client, id)instead ofapi.getBuild(id)ApiServicepreserved as wrapper for backwards compatibilityTesting Approach
tests/api/core.spec.jstests/api/client.spec.jstests/api/endpoints.spec.jstests/server/handlers/api-handler.spec.jsStats
Test plan
npm run lintpassesnpm run buildpassesnpm test- all 936 tests pass