@addon24/eslint-config
Version:
ESLint configuration rules for WorldOfTextcraft projects - Centralized configuration for all project types
351 lines (298 loc) • 13.4 kB
text/typescript
/**
* Integration Tests für Custom ESLint Rules
*
* Diese Tests überprüfen, ob alle Custom ESLint Rules korrekt konfiguriert sind
* und die erwarteten Meta-Informationen enthalten.
*/
import fs from 'fs';
import path from 'path';
describe('Custom ESLint Rules Integration', () => {
const rulesDir = path.join(__dirname, '..');
// Test all custom rule files exist
test('should have all expected custom rule files', () => {
const expectedRules = [
'admin-controller-security.js',
'auth-guard-required.js',
'controller-naming-conventions.js',
'dto-entity-mapping-completeness.js',
'enforce-dto-usage.js',
'enforce-basecontroller.js',
'enforce-error-handling.js',
'no-import-meta-env.js',
'no-tsyringe.js',
'entity-required-properties.js',
'enforce-api-versioning.js',
'no-dto-constructors.js',
'prefer-dto-classes.js',
'require-dto-response.js',
'analyze-relation-usage.js',
'service-architecture.js',
'enforce-database-transaction-safety.js',
'enforce-result-pattern.js',
'dead-code-detection.js',
'no-magic-values.js',
'enforce-test-coverage.js',
'controller-swagger-complete.js',
'controller-swagger-docs.js',
'max-classes-per-file.js',
'no-entity-in-swagger-docs.js',
'dto-visibility-modifiers.js',
'dto-entity-swagger-separation.js',
'coordinate-naming.js',
'no-type-assertion.js',
'websocket-architecture.js',
'controller-architecture.js',
'controller-swagger-english.js',
'entity-to-dto-test.js',
'no-entity-imports-in-controllers.js',
'controller-readonly-restriction.js',
'enforce-dto-from-entity.js',
'require-valid-relations.js',
'eslint-plugin-no-comments.js',
];
expectedRules.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
expect(fs.existsSync(rulePath)).toBe(true);
});
});
// Test rule file structure with different export formats
test('should have valid rule file structure', () => {
const ruleFiles = fs.readdirSync(rulesDir)
.filter(file => file.endsWith('.js') && !file.includes('test'))
.filter(file => !['errors.js', 'best-practices.js', 'style.js', 'imports.js', 'jest.js', 'node.js', 'strict.js', 'variables.js', 'typescript.js', 'es6.js', 'deprecate.js', 'prefer-dto-create-method.js'].includes(file));
ruleFiles.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
// Check for either export default { rules: {...} } or export default ruleObject
const hasExportDefault = content.includes('export default {') ||
content.includes('export default') ||
content.match(/export default\s+\w+Rule/) ||
content.match(/export default\s+[a-zA-Z][a-zA-Z0-9]*;/) ||
content.match(/export default\s+\w+;/);
expect(hasExportDefault).toBeTruthy();
// Check for meta information in rule definitions (skip for some rule formats)
if (!content.includes('export default {')) {
expect(content).toMatch(/meta:\s*{/);
expect(content).toMatch(/type:\s*["'].*["']/);
expect(content).toMatch(/docs:\s*{/);
}
// Check for create function (only for custom rules, not config files)
if (!content.includes('export default {')) {
expect(content).toMatch(/create/);
}
});
});
// Test specific critical rules
describe('Critical Rule Validations', () => {
test('admin-controller-security rule should enforce security', () => {
const rulePath = path.join(rulesDir, 'admin-controller-security.js');
const content = fs.readFileSync(rulePath, 'utf8');
// Should check for AuthGuard and AdminGuard
expect(content).toMatch(/AuthGuard/);
expect(content).toMatch(/AdminGuard/);
expect(content).toMatch(/UseGuards/);
expect(content).toMatch(/ApiBearerAuth/);
});
test('enforce-dto-usage rule should prevent anonymous objects', () => {
const rulePath = path.join(rulesDir, 'enforce-dto-usage.js');
const content = fs.readFileSync(rulePath, 'utf8');
// Should check for ObjectExpression and suggest DTOs
expect(content).toMatch(/ObjectExpression/);
expect(content).toMatch(/anonymousObject/);
expect(content).toMatch(/DTO|Dto/);
});
test('no-import-meta-env rule should enforce ConfigProvider', () => {
const rulePath = path.join(rulesDir, 'no-import-meta-env.js');
const content = fs.readFileSync(rulePath, 'utf8');
// Should enforce ConfigProvider usage
expect(content).toMatch(/ConfigProvider/);
expect(content).toMatch(/import\.meta\.env/);
});
test('enforce-error-handling rule should require try-catch', () => {
const rulePath = path.join(rulesDir, 'enforce-error-handling.js');
const content = fs.readFileSync(rulePath, 'utf8');
// Should check for try-catch in async methods
expect(content).toMatch(/async/);
expect(content).toMatch(/TryStatement/);
expect(content).toMatch(/logger\.error/);
});
});
// Test rule message IDs
test('should have meaningful error message IDs', () => {
const criticalRules = [
'admin-controller-security.js',
'auth-guard-required.js',
'enforce-dto-usage.js',
'enforce-error-handling.js',
];
criticalRules.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
// Should have messages object with meaningful IDs
expect(content).toMatch(/messages:\s*{/);
// Message IDs should be descriptive
const messageMatches = content.match(/["']([a-zA-Z][a-zA-Z0-9]*(?:[A-Z][a-z0-9]*)*?)["']:\s*["']/g);
if (messageMatches) {
messageMatches.forEach(match => {
const messageId = match.match(/["']([^"']*?)["']:/)?.[1];
if (messageId) {
// Message IDs should be camelCase and descriptive
expect(messageId).toMatch(/^[a-z][a-zA-Z0-9]*$/);
expect(messageId.length).toBeGreaterThan(5); // Should be descriptive
}
});
}
});
});
// Test that individual rules don't have internal conflicts (fixed to check within files)
test('should not have conflicting rule names within individual files', () => {
const ruleFiles = fs.readdirSync(rulesDir)
.filter(file => file.endsWith('.js') && !file.includes('test'));
ruleFiles.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
const ruleNames = new Set<string>();
// Extract rule names from the rules object (for files that export { rules: {...} })
const rulesMatch = content.match(/rules:\s*{([^}]+)}/s);
if (rulesMatch) {
const rulesContent = rulesMatch[1];
const ruleNameMatches = rulesContent?.match(/["']([^"']+)["']:/g);
if (ruleNameMatches) {
ruleNameMatches.forEach(match => {
const ruleName = match.match(/["']([^"']+)["']:/)?.[1];
if (ruleName && !ruleName.includes('//')) {
expect(ruleNames.has(ruleName)).toBe(false);
ruleNames.add(ruleName);
}
});
}
}
});
});
// Test rule functionality assumptions
describe('Rule Functionality Validation', () => {
test('controller rules should target controller files', () => {
const controllerRules = [
'admin-controller-security.js',
'auth-guard-required.js',
'controller-naming-conventions.js',
'enforce-basecontroller.js',
'controller-architecture.js',
'controller-swagger-complete.js',
'controller-swagger-docs.js',
'controller-swagger-english.js',
'controller-readonly-restriction.js',
];
controllerRules.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
// Should check for controller files
expect(content).toMatch(/[Cc]ontroller/);
});
});
test('DTO rules should enforce data transfer patterns', () => {
const dtoRules = [
'enforce-dto-usage.js',
'dto-entity-mapping-completeness.js',
'no-dto-constructors.js',
'prefer-dto-classes.js',
'require-dto-response.js',
'dto-visibility-modifiers.js',
'dto-entity-swagger-separation.js',
'enforce-dto-from-entity.js',
];
dtoRules.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
// Should reference DTO patterns
expect(content).toMatch(/[Dd]to|DTO/);
});
});
test('security rules should check authentication and authorization', () => {
const securityRules = [
'admin-controller-security.js',
'auth-guard-required.js',
];
securityRules.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
// Should check for guards and auth
expect(content).toMatch(/[Gg]uard|[Aa]uth/);
});
});
test('entity rules should enforce database patterns', () => {
const entityRules = [
'entity-required-properties.js',
'no-entity-imports-in-controllers.js',
'no-entity-in-swagger-docs.js',
'entity-to-dto-test.js',
'require-valid-relations.js',
'analyze-relation-usage.js',
];
entityRules.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
// Should reference entity patterns
expect(content).toMatch(/[Ee]ntity|Entity/);
});
});
test('service architecture rules should enforce service patterns', () => {
const serviceRules = [
'service-architecture.js',
'websocket-architecture.js',
'enforce-database-transaction-safety.js',
'enforce-result-pattern.js',
];
serviceRules.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
// Should reference service or architecture patterns
expect(content).toMatch(/[Ss]ervice|[Aa]rchitecture|[Tt]ransaction|[Rr]esult/);
});
});
});
// Test rule categories and organization
test('should properly categorize rules by type', () => {
const ruleCategories = {
security: ['admin-controller-security.js', 'auth-guard-required.js'],
architecture: ['enforce-dto-usage.js', 'enforce-basecontroller.js', 'dto-entity-mapping-completeness.js', 'service-architecture.js', 'controller-architecture.js', 'websocket-architecture.js'],
codeQuality: ['enforce-error-handling.js', 'no-magic-values.js', 'dead-code-detection.js', 'enforce-test-coverage.js'],
bestPractices: ['no-import-meta-env.js', 'no-tsyringe.js', 'prefer-dto-classes.js', 'coordinate-naming.js', 'no-type-assertion.js', 'max-classes-per-file.js'],
api: ['enforce-api-versioning.js', 'controller-swagger-complete.js', 'controller-swagger-docs.js', 'controller-swagger-english.js'],
};
Object.entries(ruleCategories).forEach(([category, rules]) => {
rules.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
expect(fs.existsSync(rulePath)).toBe(true);
const content = fs.readFileSync(rulePath, 'utf8');
// Should have appropriate category in docs (optional for some rule formats)
if (content.includes('category:')) {
expect(content).toMatch(/category:\s*["'][^"']*["']/);
}
});
});
});
// Test that all custom rules have proper structure
test('should have consistent rule structure across all files', () => {
const customRuleFiles = fs.readdirSync(rulesDir)
.filter(file => file.endsWith('.js') && !file.includes('test'))
.filter(file => !['errors.js', 'best-practices.js', 'style.js', 'imports.js', 'jest.js', 'node.js', 'strict.js', 'variables.js', 'typescript.js', 'es6.js', 'deprecate.js'].includes(file));
expect(customRuleFiles.length).toBeGreaterThan(30); // Should have substantial number of custom rules
customRuleFiles.forEach(ruleFile => {
const rulePath = path.join(rulesDir, ruleFile);
const content = fs.readFileSync(rulePath, 'utf8');
// Each rule should have documentation (only for custom rules, not config files)
if (!content.includes('export default {')) {
expect(content).toMatch(/description:/);
// Category is optional for some rules
if (content.includes('category:')) {
expect(content).toMatch(/category:/);
}
}
// Each rule should have messages for error reporting (optional for some formats)
if (!content.includes('export default {') && !ruleFile.includes('enum-database-validation')) {
expect(content).toMatch(/messages:/);
}
});
});
});