import { describe, it, expect } from 'vitest';
import { parser } from './http.parser';
/**
* HTTP Grammar 测试
*
* 测试目标:验证标准的 HTTP 请求语法是否能正确解析,不应该出现错误节点(⚠)
*/
describe('HTTP Grammar 解析测试', () => {
/**
* 辅助函数:解析代码并返回语法树
*/
function parseCode(code: string) {
const tree = parser.parse(code);
return tree;
}
/**
* 辅助函数:检查语法树中是否有错误节点
*/
function hasErrorNodes(tree: any): { hasError: boolean; errors: Array<{ name: string; from: number; to: number; text: string }> } {
const errors: Array<{ name: string; from: number; to: number; text: string }> = [];
tree.iterate({
enter: (node: any) => {
if (node.name === '⚠') {
errors.push({
name: node.name,
from: node.from,
to: node.to,
text: tree.toString().substring(node.from, node.to)
});
}
}
});
return {
hasError: errors.length > 0,
errors
};
}
/**
* 辅助函数:打印语法树结构(用于调试)
*/
function printTree(tree: any, code: string, maxDepth = 5) {
const lines: string[] = [];
tree.iterate({
enter: (node: any) => {
const depth = getNodeDepth(tree, node);
if (depth > maxDepth) return false; // 限制深度
const indent = ' '.repeat(depth);
const text = code.substring(node.from, Math.min(node.to, node.from + 30));
const displayText = text.length === 30 ? text + '...' : text;
lines.push(`${indent}${node.name} [${node.from}-${node.to}]: "${displayText.replace(/\n/g, '\\n')}"`);
}
});
return lines.join('\n');
}
/**
* 获取节点深度
*/
function getNodeDepth(tree: any, targetNode: any): number {
let depth = 0;
let cursor = tree.cursor();
function traverse(node: any, currentDepth: number): boolean {
if (node.from === targetNode.from && node.to === targetNode.to && node.name === targetNode.name) {
depth = currentDepth;
return true;
}
return false;
}
// 简化:假设深度就是节点的层级
let current = targetNode;
while (current.parent) {
depth++;
current = current.parent;
}
return depth;
}
it('应该正确解析标准的 GET 请求(包含 @json)', () => {
const code = `GET "http://127.0.0.1:80/api/create" {
host: "https://api.example.com",
content-type: "application/json",
user-agent: 'Mozilla/5.0',
@json {
name : "xxx",
test: "xx"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
// 如果有错误,打印详细信息
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
console.log('\n完整语法树:');
console.log(printTree(tree, code));
}
expect(result.hasError).toBe(false);
expect(result.errors).toHaveLength(0);
});
it('应该正确解析简单的 POST 请求', () => {
const code = `POST "http://127.0.0.1/api" {
host: "example.com",
content-type: "application/json"
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
console.log('\n完整语法树:');
console.log(printTree(tree, code));
}
expect(result.hasError).toBe(false);
});
it('应该正确解析带嵌套块的请求', () => {
const code = `POST "http://test.com" {
@json {
user: {
name: "test",
age: 25
}
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
console.log('\n完整语法树:');
console.log(printTree(tree, code));
}
expect(result.hasError).toBe(false);
});
it('应该正确识别 RequestStatement 节点', () => {
const code = `GET "http://test.com" {
host: "test.com"
}`;
const tree = parseCode(code);
let hasRequestStatement = false;
let hasMethod = false;
let hasUrl = false;
let hasBlock = false;
tree.iterate({
enter: (node: any) => {
if (node.name === 'RequestStatement') hasRequestStatement = true;
if (node.name === 'Method' || node.name === 'GET') hasMethod = true;
if (node.name === 'Url') hasUrl = true;
if (node.name === 'Block') hasBlock = true;
}
});
expect(hasRequestStatement).toBe(true);
expect(hasMethod).toBe(true);
expect(hasUrl).toBe(true);
expect(hasBlock).toBe(true);
});
it('应该正确解析多个连续的请求', () => {
const code = `GET "http://test1.com" {
host: "test1.com"
}
POST "http://test2.com" {
host: "test2.com"
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
// 统计 RequestStatement 数量
let requestCount = 0;
tree.iterate({
enter: (node: any) => {
if (node.name === 'RequestStatement') requestCount++;
}
});
expect(requestCount).toBe(2);
});
it('错误语法:方法名拼写错误(应该产生错误)', () => {
const code = `Gef "http://test.com" {
host: "test.com"
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
// 这个应该有错误
expect(result.hasError).toBe(true);
});
it('错误语法:花括号不匹配(应该产生错误)', () => {
const code = `GET "http://test.com" {
host: "test.com"`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
// 这个应该有错误
expect(result.hasError).toBe(true);
});
it('应该支持属性后面不加逗号', () => {
const code = `GET "http://test.com" {
host: "test.com"
content-type: "application/json"
user-agent: "Mozilla/5.0"
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
console.log('\n完整语法树:');
console.log(printTree(tree, code));
}
expect(result.hasError).toBe(false);
});
it('应该支持 @json 块后面不加逗号(JSON块内部必须用逗号)', () => {
const code = `POST "http://test.com" {
host: "test.com"
@json {
name: "xxx",
test: "xx"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
console.log('\n完整语法树:');
console.log(printTree(tree, code));
}
expect(result.hasError).toBe(false);
});
it('应该支持混合使用逗号(有些有逗号,有些没有)', () => {
const code = `POST "http://test.com" {
host: "test.com",
content-type: "application/json"
user-agent: "Mozilla/5.0",
@json {
name: "xxx",
test: "xx"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('用户提供的真实示例(HTTP 属性不用逗号,JSON 块内必须用逗号)', () => {
const code = `GET "http://127.0.0.1:80/api/create" {
host: "https://api.example.com"
content-type: "application/json"
user-agent: 'Mozilla/5.0'
@json {
name: "xxx",
test: "xx"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
console.log('\n完整语法树:');
console.log(printTree(tree, code));
}
expect(result.hasError).toBe(false);
});
it('JSON 块内缺少逗号应该报错', () => {
const code = `POST "http://test.com" {
@json {
name: "xxx"
test: "xx"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
// JSON 块内缺少逗号,应该有错误
expect(result.hasError).toBe(true);
});
it('支持 @formdata 块(必须使用逗号)', () => {
const code = `POST "http://test.com" {
@formdata {
file: "test.png",
description: "test file"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('支持 JSON 嵌套对象', () => {
const code = `POST "http://test.com" {
@json {
user: {
name: "test",
age: 25
},
settings: {
theme: "dark"
}
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 发现错误节点:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
});
describe('HTTP 请求体格式测试', () => {
/**
* 辅助函数:解析代码并返回语法树
*/
function parseCode(code: string) {
const tree = parser.parse(code);
return tree;
}
/**
* 辅助函数:检查语法树中是否有错误节点
*/
function hasErrorNodes(tree: any): { hasError: boolean; errors: Array<{ name: string; from: number; to: number; text: string }> } {
const errors: Array<{ name: string; from: number; to: number; text: string }> = [];
tree.iterate({
enter: (node: any) => {
if (node.name === '⚠') {
errors.push({
name: node.name,
from: node.from,
to: node.to,
text: tree.toString().substring(node.from, node.to)
});
}
}
});
return {
hasError: errors.length > 0,
errors
};
}
it('✅ @json - JSON 格式请求体', () => {
const code = `POST "http://api.example.com/users" {
content-type: "application/json"
authorization: "Bearer token123"
@json {
name: "张三",
age: 25,
email: "zhangsan@example.com",
address: {
city: "北京",
street: "长安街"
},
tags: {
skill: "TypeScript",
level: "advanced"
}
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ @json 格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('✅ @formdata - 表单数据格式', () => {
const code = `POST "http://api.example.com/upload" {
content-type: "multipart/form-data"
@formdata {
file: "avatar.png",
username: "zhangsan",
email: "zhangsan@example.com",
age: 25,
description: "用户头像上传"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ @formdata 格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('✅ @urlencoded - URL 编码格式', () => {
const code = `POST "http://api.example.com/login" {
content-type: "application/x-www-form-urlencoded"
@urlencoded {
username: "admin",
password: "123456",
remember: true
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ @urlencoded 格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('✅ @text - 纯文本请求体', () => {
const code = `POST "http://api.example.com/webhook" {
content-type: "text/plain"
@text {
content: "这是一段纯文本内容,可以包含多行\\n支持中文和特殊字符!@#$%"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ @text 格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('✅ # 单行注释', () => {
const code = `# 这是一个用户登录接口
POST "http://api.example.com/login" {
# 添加认证头
content-type: "application/json"
# 登录参数
@json {
username: "admin",
password: "123456"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 单行注释格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('✅ 混合多种格式 - JSON 请求', () => {
const code = `POST "http://api.example.com/login" {
content-type: "application/json"
user-agent: "Mozilla/5.0"
@json {
username: "admin",
password: "123456"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 混合格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('✅ 复杂嵌套 JSON', () => {
const code = `POST "http://api.example.com/complex" {
@json {
user: {
profile: {
name: "张三",
contact: {
email: "zhangsan@example.com",
phone: "13800138000"
}
},
settings: {
theme: "dark",
language: "zh-CN"
}
},
metadata: {
version: "1.0",
timestamp: 1234567890
}
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 复杂嵌套格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('✅ 多个请求(不同格式 + 注释)', () => {
const code = `# JSON 请求
POST "http://api.example.com/json" {
@json {
name: "test"
}
}
# FormData 请求
POST "http://api.example.com/form" {
@formdata {
file: "test.txt",
description: "test"
}
}
# URLEncoded 请求
POST "http://api.example.com/login" {
@urlencoded {
username: "admin",
password: "123456"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ 多请求格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
});
describe('HTTP 新格式测试 - params/xml/html/javascript/binary', () => {
function parseCode(code: string) {
const tree = parser.parse(code);
return tree;
}
function hasErrorNodes(tree: any): { hasError: boolean; errors: Array<{ name: string; from: number; to: number; text: string }> } {
const errors: Array<{ name: string; from: number; to: number; text: string }> = [];
tree.iterate({
enter: (node: any) => {
if (node.name === '⚠') {
errors.push({
name: node.name,
from: node.from,
to: node.to,
text: tree.toString().substring(node.from, node.to)
});
}
}
});
return {
hasError: errors.length > 0,
errors
};
}
it('✅ @params - URL 参数格式', () => {
const code = `GET "http://api.example.com/users" {
authorization: "Bearer token123"
@params {
page: 1,
size: 20,
keyword: "张三",
status: "active"
}
}`;
const tree = parseCode(code);
const result = hasErrorNodes(tree);
if (result.hasError) {
console.log('\n❌ @params 格式错误:');
result.errors.forEach(err => {
console.log(` - ${err.name} at ${err.from}-${err.to}: "${err.text}"`);
});
}
expect(result.hasError).toBe(false);
});
it('✅ @xml - XML 格式', () => {
const code = `POST "http://api.example.com/soap" {
content-type: "application/xml"
@xml {
xml: "
内容