mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 02:39:19 -05:00
feat: add Jest testing framework and CI/CD configuration (#187)
Co-authored-by: samanhappy@qq.com <my6051199>
This commit is contained in:
180
tests/utils/pathLogic.test.ts
Normal file
180
tests/utils/pathLogic.test.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
// Test for path utilities functionality
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// Mock fs module
|
||||
jest.mock('fs');
|
||||
const mockFs = fs as jest.Mocked<typeof fs>;
|
||||
|
||||
describe('Path Utilities Logic', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
delete process.env.MCPHUB_SETTING_PATH;
|
||||
});
|
||||
|
||||
// Test the core logic of path resolution
|
||||
const findConfigFile = (filename: string): string => {
|
||||
const envPath = process.env.MCPHUB_SETTING_PATH;
|
||||
const potentialPaths = [
|
||||
...(envPath ? [envPath] : []),
|
||||
path.resolve(process.cwd(), filename),
|
||||
path.join(process.cwd(), filename),
|
||||
];
|
||||
|
||||
for (const filePath of potentialPaths) {
|
||||
if (fs.existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
return path.resolve(process.cwd(), filename);
|
||||
};
|
||||
|
||||
describe('Configuration File Resolution', () => {
|
||||
it('should find existing file in current directory', () => {
|
||||
const filename = 'test-config.json';
|
||||
const expectedPath = path.resolve(process.cwd(), filename);
|
||||
|
||||
mockFs.existsSync.mockImplementation((filePath) => {
|
||||
return filePath === expectedPath;
|
||||
});
|
||||
|
||||
const result = findConfigFile(filename);
|
||||
|
||||
expect(result).toBe(expectedPath);
|
||||
expect(mockFs.existsSync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should prioritize environment variable path', () => {
|
||||
const filename = 'test-config.json';
|
||||
const envPath = '/custom/path/test-config.json';
|
||||
process.env.MCPHUB_SETTING_PATH = envPath;
|
||||
|
||||
mockFs.existsSync.mockImplementation((filePath) => {
|
||||
return filePath === envPath;
|
||||
});
|
||||
|
||||
const result = findConfigFile(filename);
|
||||
|
||||
expect(result).toBe(envPath);
|
||||
expect(mockFs.existsSync).toHaveBeenCalledWith(envPath);
|
||||
});
|
||||
|
||||
it('should return default path when file does not exist', () => {
|
||||
const filename = 'nonexistent-config.json';
|
||||
const expectedDefaultPath = path.resolve(process.cwd(), filename);
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false);
|
||||
|
||||
const result = findConfigFile(filename);
|
||||
|
||||
expect(result).toBe(expectedDefaultPath);
|
||||
});
|
||||
|
||||
it('should handle different file types', () => {
|
||||
const testFiles = [
|
||||
'config.json',
|
||||
'settings.yaml',
|
||||
'data.xml',
|
||||
'servers.json'
|
||||
];
|
||||
|
||||
testFiles.forEach(filename => {
|
||||
const expectedPath = path.resolve(process.cwd(), filename);
|
||||
|
||||
mockFs.existsSync.mockImplementation((filePath) => {
|
||||
return filePath === expectedPath;
|
||||
});
|
||||
|
||||
const result = findConfigFile(filename);
|
||||
expect(result).toBe(expectedPath);
|
||||
expect(path.isAbsolute(result)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Path Operations', () => {
|
||||
it('should generate absolute paths', () => {
|
||||
const filename = 'test.json';
|
||||
mockFs.existsSync.mockReturnValue(false);
|
||||
|
||||
const result = findConfigFile(filename);
|
||||
|
||||
expect(path.isAbsolute(result)).toBe(true);
|
||||
expect(result).toContain(filename);
|
||||
}); it('should handle path normalization', () => {
|
||||
const filename = './config/../settings.json';
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false);
|
||||
|
||||
const result = findConfigFile(filename);
|
||||
|
||||
expect(typeof result).toBe('string');
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should work consistently across multiple calls', () => {
|
||||
const filename = 'consistent-test.json';
|
||||
const expectedPath = path.resolve(process.cwd(), filename);
|
||||
|
||||
mockFs.existsSync.mockImplementation((filePath) => {
|
||||
return filePath === expectedPath;
|
||||
});
|
||||
|
||||
const result1 = findConfigFile(filename);
|
||||
const result2 = findConfigFile(filename);
|
||||
|
||||
expect(result1).toBe(result2);
|
||||
expect(result1).toBe(expectedPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Environment Variable Handling', () => {
|
||||
it('should handle missing environment variable gracefully', () => {
|
||||
const filename = 'test.json';
|
||||
delete process.env.MCPHUB_SETTING_PATH;
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false);
|
||||
|
||||
const result = findConfigFile(filename);
|
||||
|
||||
expect(typeof result).toBe('string');
|
||||
expect(result).toContain(filename);
|
||||
});
|
||||
|
||||
it('should handle empty environment variable', () => {
|
||||
const filename = 'test.json';
|
||||
process.env.MCPHUB_SETTING_PATH = '';
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false);
|
||||
|
||||
const result = findConfigFile(filename);
|
||||
|
||||
expect(typeof result).toBe('string');
|
||||
expect(result).toContain(filename);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle fs.existsSync errors gracefully', () => {
|
||||
const filename = 'test.json';
|
||||
|
||||
mockFs.existsSync.mockImplementation(() => {
|
||||
throw new Error('File system error');
|
||||
});
|
||||
|
||||
expect(() => findConfigFile(filename)).toThrow('File system error');
|
||||
});
|
||||
|
||||
it('should validate input parameters', () => {
|
||||
const emptyFilename = '';
|
||||
|
||||
mockFs.existsSync.mockReturnValue(false);
|
||||
|
||||
const result = findConfigFile(emptyFilename);
|
||||
|
||||
expect(typeof result).toBe('string');
|
||||
// Should still return a path, even for empty filename
|
||||
});
|
||||
});
|
||||
});
|
||||
176
tests/utils/testHelpers.ts
Normal file
176
tests/utils/testHelpers.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
// Test utilities and helpers
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export interface TestUser {
|
||||
username: string;
|
||||
password: string;
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
export interface AuthTokens {
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test Express app instance
|
||||
*/
|
||||
export const createTestApp = (): express.Application => {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
return app;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a test JWT token
|
||||
*/
|
||||
export const generateTestToken = (payload: any, secret = 'test-jwt-secret-key'): string => {
|
||||
return jwt.sign(payload, secret, { expiresIn: '1h' });
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a test user token with default claims
|
||||
*/
|
||||
export const createUserToken = (username = 'testuser', isAdmin = false): string => {
|
||||
const payload = {
|
||||
user: {
|
||||
username,
|
||||
isAdmin,
|
||||
},
|
||||
};
|
||||
return generateTestToken(payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an admin user token
|
||||
*/
|
||||
export const createAdminToken = (username = 'admin'): string => {
|
||||
return createUserToken(username, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make authenticated request helper
|
||||
*/
|
||||
export const makeAuthenticatedRequest = (app: express.Application, token: string) => {
|
||||
return {
|
||||
get: (url: string) => request(app).get(url).set('Authorization', `Bearer ${token}`),
|
||||
post: (url: string) => request(app).post(url).set('Authorization', `Bearer ${token}`),
|
||||
put: (url: string) => request(app).put(url).set('Authorization', `Bearer ${token}`),
|
||||
delete: (url: string) => request(app).delete(url).set('Authorization', `Bearer ${token}`),
|
||||
patch: (url: string) => request(app).patch(url).set('Authorization', `Bearer ${token}`),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Common test data generators
|
||||
*/
|
||||
export const TestData = {
|
||||
user: (overrides: Partial<TestUser> = {}): TestUser => ({
|
||||
username: 'testuser',
|
||||
password: 'password123',
|
||||
isAdmin: false,
|
||||
...overrides,
|
||||
}),
|
||||
|
||||
adminUser: (overrides: Partial<TestUser> = {}): TestUser => ({
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
isAdmin: true,
|
||||
...overrides,
|
||||
}),
|
||||
|
||||
serverConfig: (overrides: any = {}) => ({
|
||||
type: 'openapi',
|
||||
openapi: {
|
||||
url: 'https://api.example.com/openapi.json',
|
||||
version: '3.1.0',
|
||||
security: {
|
||||
type: 'none',
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
...overrides,
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock response helpers
|
||||
*/
|
||||
export const MockResponse = {
|
||||
success: (data: any = {}) => ({
|
||||
success: true,
|
||||
data,
|
||||
}),
|
||||
|
||||
error: (message: string, code = 400) => ({
|
||||
success: false,
|
||||
message,
|
||||
code,
|
||||
}),
|
||||
|
||||
validation: (errors: any[]) => ({
|
||||
success: false,
|
||||
errors,
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* Database test helpers
|
||||
*/
|
||||
export const DbHelpers = {
|
||||
/**
|
||||
* Clear all test data from database
|
||||
*/
|
||||
clearDatabase: async (): Promise<void> => {
|
||||
// TODO: Implement based on your database setup
|
||||
console.log('Clearing test database...');
|
||||
},
|
||||
|
||||
/**
|
||||
* Seed test data
|
||||
*/
|
||||
seedTestData: async (): Promise<void> => {
|
||||
// TODO: Implement based on your database setup
|
||||
console.log('Seeding test data...');
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for async operations to complete
|
||||
*/
|
||||
export const waitFor = (ms: number): Promise<void> => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
/**
|
||||
* Assert API response structure
|
||||
*/
|
||||
export const expectApiResponse = (response: any) => ({
|
||||
toBeSuccess: (expectedData?: any) => {
|
||||
expect(response.body).toHaveProperty('success', true);
|
||||
if (expectedData) {
|
||||
expect(response.body.data).toEqual(expectedData);
|
||||
}
|
||||
},
|
||||
|
||||
toBeError: (expectedMessage?: string, expectedCode?: number) => {
|
||||
expect(response.body).toHaveProperty('success', false);
|
||||
if (expectedMessage) {
|
||||
expect(response.body.message).toContain(expectedMessage);
|
||||
}
|
||||
if (expectedCode) {
|
||||
expect(response.status).toBe(expectedCode);
|
||||
}
|
||||
},
|
||||
|
||||
toHaveValidationErrors: () => {
|
||||
expect(response.body).toHaveProperty('success', false);
|
||||
expect(response.body).toHaveProperty('errors');
|
||||
expect(Array.isArray(response.body.errors)).toBe(true);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user