From 818f9878aa952c7ea84c7fc5d385576cd634b118 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 17:58:47 +0000 Subject: [PATCH] Add identifier-safe Flask blueprint tests --- .../__tests__/generateFlaskBlueprint.test.ts | 73 +++++++++++++++++++ src/lib/generators/generateFlaskBlueprint.ts | 17 ++++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/lib/generators/__tests__/generateFlaskBlueprint.test.ts diff --git a/src/lib/generators/__tests__/generateFlaskBlueprint.test.ts b/src/lib/generators/__tests__/generateFlaskBlueprint.test.ts new file mode 100644 index 0000000..6db0602 --- /dev/null +++ b/src/lib/generators/__tests__/generateFlaskBlueprint.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest' + +import type { FlaskBlueprint } from '@/types/project' + +import { generateFlaskBlueprint } from '../generateFlaskBlueprint' + +const isValidIdentifier = (name: string): boolean => /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) + +const extractBlueprintVariable = (code: string): { variable: string; name: string } => { + const match = code.match(/^([A-Za-z_][A-Za-z0-9_]*)_bp = Blueprint\('([^']+)'/m) + if (!match) { + throw new Error('Blueprint definition not found.') + } + return { variable: `${match[1]}_bp`, name: match[2] } +} + +const extractFunctionNames = (code: string): string[] => { + return Array.from(code.matchAll(/^def ([A-Za-z_][A-Za-z0-9_]*)\(\):/gm)).map(match => match[1]) +} + +const extractDecoratorBlueprints = (code: string): string[] => { + return Array.from(code.matchAll(/^@([A-Za-z_][A-Za-z0-9_]*)\.route/gm)).map(match => match[1]) +} + +describe('generateFlaskBlueprint identifier sanitization', () => { + it('creates valid, consistent identifiers for tricky endpoint names', () => { + const blueprint: FlaskBlueprint = { + id: 'bp-1', + name: 'User Auth', + urlPrefix: '/auth', + description: 'Auth endpoints', + endpoints: [ + { + id: 'ep-1', + name: 'get-user', + description: 'Fetch a user', + method: 'GET', + path: '/user' + }, + { + id: 'ep-2', + name: '2fa', + description: 'Two factor auth', + method: 'POST', + path: '/2fa' + }, + { + id: 'ep-3', + name: 'user.v1', + description: 'User v1 endpoint', + method: 'GET', + path: '/user/v1' + } + ] + } + + const code = generateFlaskBlueprint(blueprint) + const blueprintDefinition = extractBlueprintVariable(code) + const functionNames = extractFunctionNames(code) + const decoratorBlueprints = extractDecoratorBlueprints(code) + + expect(isValidIdentifier(blueprintDefinition.name)).toBe(true) + expect(isValidIdentifier(blueprintDefinition.variable)).toBe(true) + expect(blueprintDefinition.variable).toBe('user_auth_bp') + expect(blueprintDefinition.name).toBe('user_auth') + expect(new Set(decoratorBlueprints)).toEqual(new Set([blueprintDefinition.variable])) + + expect(functionNames).toEqual(['get_user', '_2fa', 'user_v1']) + functionNames.forEach(name => { + expect(isValidIdentifier(name)).toBe(true) + }) + }) +}) diff --git a/src/lib/generators/generateFlaskBlueprint.ts b/src/lib/generators/generateFlaskBlueprint.ts index 301efae..4e3c162 100644 --- a/src/lib/generators/generateFlaskBlueprint.ts +++ b/src/lib/generators/generateFlaskBlueprint.ts @@ -1,14 +1,27 @@ import { FlaskBlueprint } from '@/types/project' +function toPythonIdentifier(value: string, fallback: string): string { + const normalized = value + .toLowerCase() + .replace(/[^a-z0-9_]/g, '_') + .replace(/_+/g, '_') + .replace(/^_+|_+$/g, '') + let safe = normalized || fallback + if (/^[0-9]/.test(safe)) { + safe = `_${safe}` + } + return safe +} + export function generateFlaskBlueprint(blueprint: FlaskBlueprint): string { let code = `from flask import Blueprint, request, jsonify\n` code += `from typing import Dict, Any\n\n` - const blueprintVarName = blueprint.name.toLowerCase().replace(/\s+/g, '_') + const blueprintVarName = toPythonIdentifier(blueprint.name, 'blueprint') code += `${blueprintVarName}_bp = Blueprint('${blueprintVarName}', __name__, url_prefix='${blueprint.urlPrefix}')\n\n` blueprint.endpoints.forEach(endpoint => { - const functionName = endpoint.name.toLowerCase().replace(/\s+/g, '_') + const functionName = toPythonIdentifier(endpoint.name, 'endpoint') code += `@${blueprintVarName}_bp.route('${endpoint.path}', methods=['${endpoint.method}'])\n` code += `def ${functionName}():\n` code += ` """\n`