@skillsmith/core 0.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/__tests__/incremental.test.d.ts +13 -0
- package/dist/src/analysis/__tests__/incremental.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/incremental.test.js +515 -0
- package/dist/src/analysis/__tests__/incremental.test.js.map +1 -0
- package/dist/src/analysis/__tests__/integration.test.d.ts +14 -0
- package/dist/src/analysis/__tests__/integration.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/integration.test.js +1059 -0
- package/dist/src/analysis/__tests__/integration.test.js.map +1 -0
- package/dist/src/analysis/__tests__/metrics.test.d.ts +9 -0
- package/dist/src/analysis/__tests__/metrics.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/metrics.test.js +369 -0
- package/dist/src/analysis/__tests__/metrics.test.js.map +1 -0
- package/dist/src/analysis/__tests__/performance.test.d.ts +15 -0
- package/dist/src/analysis/__tests__/performance.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/performance.test.js +402 -0
- package/dist/src/analysis/__tests__/performance.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/go.test.d.ts +12 -0
- package/dist/src/analysis/adapters/__tests__/go.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/go.test.js +561 -0
- package/dist/src/analysis/adapters/__tests__/go.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/python.test.d.ts +11 -0
- package/dist/src/analysis/adapters/__tests__/python.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/python.test.js +669 -0
- package/dist/src/analysis/adapters/__tests__/python.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.d.ts +12 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.js +676 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts +14 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.js +381 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.js.map +1 -0
- package/dist/src/analysis/adapters/base.d.ts +83 -0
- package/dist/src/analysis/adapters/base.d.ts.map +1 -0
- package/dist/src/analysis/adapters/base.js +40 -0
- package/dist/src/analysis/adapters/base.js.map +1 -0
- package/dist/src/analysis/adapters/factory.d.ts +150 -0
- package/dist/src/analysis/adapters/factory.d.ts.map +1 -0
- package/dist/src/analysis/adapters/factory.js +244 -0
- package/dist/src/analysis/adapters/factory.js.map +1 -0
- package/dist/src/analysis/adapters/go.d.ts +131 -0
- package/dist/src/analysis/adapters/go.d.ts.map +1 -0
- package/dist/src/analysis/adapters/go.js +414 -0
- package/dist/src/analysis/adapters/go.js.map +1 -0
- package/dist/src/analysis/adapters/index.d.ts +20 -0
- package/dist/src/analysis/adapters/index.d.ts.map +1 -0
- package/dist/src/analysis/adapters/index.js +23 -0
- package/dist/src/analysis/adapters/index.js.map +1 -0
- package/dist/src/analysis/adapters/java.d.ts +154 -0
- package/dist/src/analysis/adapters/java.d.ts.map +1 -0
- package/dist/src/analysis/adapters/java.js +407 -0
- package/dist/src/analysis/adapters/java.js.map +1 -0
- package/dist/src/analysis/adapters/python.d.ts +165 -0
- package/dist/src/analysis/adapters/python.d.ts.map +1 -0
- package/dist/src/analysis/adapters/python.js +475 -0
- package/dist/src/analysis/adapters/python.js.map +1 -0
- package/dist/src/analysis/adapters/rust.d.ts +116 -0
- package/dist/src/analysis/adapters/rust.d.ts.map +1 -0
- package/dist/src/analysis/adapters/rust.js +476 -0
- package/dist/src/analysis/adapters/rust.js.map +1 -0
- package/dist/src/analysis/adapters/typescript.d.ts +68 -0
- package/dist/src/analysis/adapters/typescript.d.ts.map +1 -0
- package/dist/src/analysis/adapters/typescript.js +79 -0
- package/dist/src/analysis/adapters/typescript.js.map +1 -0
- package/dist/src/analysis/aggregator.d.ts +193 -0
- package/dist/src/analysis/aggregator.d.ts.map +1 -0
- package/dist/src/analysis/aggregator.js +283 -0
- package/dist/src/analysis/aggregator.js.map +1 -0
- package/dist/src/analysis/cache.d.ts +180 -0
- package/dist/src/analysis/cache.d.ts.map +1 -0
- package/dist/src/analysis/cache.js +279 -0
- package/dist/src/analysis/cache.js.map +1 -0
- package/dist/src/analysis/file-streamer.d.ts +136 -0
- package/dist/src/analysis/file-streamer.d.ts.map +1 -0
- package/dist/src/analysis/file-streamer.js +291 -0
- package/dist/src/analysis/file-streamer.js.map +1 -0
- package/dist/src/analysis/incremental-parser.d.ts +186 -0
- package/dist/src/analysis/incremental-parser.d.ts.map +1 -0
- package/dist/src/analysis/incremental-parser.js +291 -0
- package/dist/src/analysis/incremental-parser.js.map +1 -0
- package/dist/src/analysis/incremental.d.ts +186 -0
- package/dist/src/analysis/incremental.d.ts.map +1 -0
- package/dist/src/analysis/incremental.js +247 -0
- package/dist/src/analysis/incremental.js.map +1 -0
- package/dist/src/analysis/index.d.ts +25 -3
- package/dist/src/analysis/index.d.ts.map +1 -1
- package/dist/src/analysis/index.js +45 -3
- package/dist/src/analysis/index.js.map +1 -1
- package/dist/src/analysis/language-detector.d.ts +92 -0
- package/dist/src/analysis/language-detector.d.ts.map +1 -0
- package/dist/src/analysis/language-detector.js +602 -0
- package/dist/src/analysis/language-detector.js.map +1 -0
- package/dist/src/analysis/memory-monitor.d.ts +199 -0
- package/dist/src/analysis/memory-monitor.d.ts.map +1 -0
- package/dist/src/analysis/memory-monitor.js +271 -0
- package/dist/src/analysis/memory-monitor.js.map +1 -0
- package/dist/src/analysis/metrics.d.ts +300 -0
- package/dist/src/analysis/metrics.d.ts.map +1 -0
- package/dist/src/analysis/metrics.js +537 -0
- package/dist/src/analysis/metrics.js.map +1 -0
- package/dist/src/analysis/router.d.ts +264 -0
- package/dist/src/analysis/router.d.ts.map +1 -0
- package/dist/src/analysis/router.js +398 -0
- package/dist/src/analysis/router.js.map +1 -0
- package/dist/src/analysis/tree-cache.d.ts +208 -0
- package/dist/src/analysis/tree-cache.d.ts.map +1 -0
- package/dist/src/analysis/tree-cache.js +288 -0
- package/dist/src/analysis/tree-cache.js.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts +141 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.js +239 -0
- package/dist/src/analysis/tree-sitter/manager.js.map +1 -0
- package/dist/src/analysis/types.d.ts +69 -6
- package/dist/src/analysis/types.d.ts.map +1 -1
- package/dist/src/analysis/types.js +23 -2
- package/dist/src/analysis/types.js.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts +141 -0
- package/dist/src/analysis/worker-pool.d.ts.map +1 -0
- package/dist/src/analysis/worker-pool.js +418 -0
- package/dist/src/analysis/worker-pool.js.map +1 -0
- package/dist/src/analytics/schema.d.ts +1 -1
- package/dist/src/analytics/schema.d.ts.map +1 -1
- package/dist/src/analytics/schema.js +72 -0
- package/dist/src/analytics/schema.js.map +1 -1
- package/dist/src/api/cache.d.ts +24 -1
- package/dist/src/api/cache.d.ts.map +1 -1
- package/dist/src/api/cache.js +50 -2
- package/dist/src/api/cache.js.map +1 -1
- package/dist/src/api/client.d.ts +132 -2
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +214 -18
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +7 -0
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/types.d.ts +251 -0
- package/dist/src/api/types.d.ts.map +1 -0
- package/dist/src/api/types.js +9 -0
- package/dist/src/api/types.js.map +1 -0
- package/dist/src/benchmarks/memory/MemoryProfiler.d.ts.map +1 -1
- package/dist/src/benchmarks/memory/MemoryProfiler.js.map +1 -1
- package/dist/src/embeddings/index.d.ts.map +1 -1
- package/dist/src/embeddings/index.js.map +1 -1
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +1 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/repositories/IndexerRepository.d.ts.map +1 -1
- package/dist/src/repositories/IndexerRepository.js +1 -0
- package/dist/src/repositories/IndexerRepository.js.map +1 -1
- package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
- package/dist/src/repositories/SkillRepository.js +1 -0
- package/dist/src/repositories/SkillRepository.js.map +1 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.d.ts.map +1 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.js.map +1 -1
- package/dist/src/repositories/quarantine/query-builder.d.ts.map +1 -1
- package/dist/src/repositories/quarantine/query-builder.js +1 -1
- package/dist/src/repositories/quarantine/query-builder.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +3 -3
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/scripts/github-import/index.js.map +1 -1
- package/dist/src/scripts/import-github-skills.js +1 -1
- package/dist/src/scripts/import-github-skills.js.map +1 -1
- package/dist/src/scripts/skill-scanner/reporter.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/reporter.js.map +1 -1
- package/dist/src/scripts/skill-scanner/scanner.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/scanner.js.map +1 -1
- package/dist/src/scripts/skill-scanner/trust-scorer.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
- package/dist/src/scripts/validation/index.js +1 -2
- package/dist/src/scripts/validation/index.js.map +1 -1
- package/dist/src/scripts/validation/pipeline.d.ts.map +1 -1
- package/dist/src/scripts/validation/pipeline.js.map +1 -1
- package/dist/src/scripts/validation/types.d.ts +2 -2
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/services/SearchService.d.ts.map +1 -1
- package/dist/src/services/SearchService.js +1 -0
- package/dist/src/services/SearchService.js.map +1 -1
- package/dist/src/session/SessionHealthMonitor.d.ts +1 -1
- package/dist/src/session/SessionHealthMonitor.d.ts.map +1 -1
- package/dist/src/session/SessionHealthMonitor.js +1 -1
- package/dist/src/session/SessionHealthMonitor.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +1 -1
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +2 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/posthog.d.ts +27 -5
- package/dist/src/telemetry/posthog.d.ts.map +1 -1
- package/dist/src/telemetry/posthog.js +20 -5
- package/dist/src/telemetry/posthog.js.map +1 -1
- package/dist/src/types/skill.d.ts +3 -0
- package/dist/src/types/skill.d.ts.map +1 -1
- package/dist/src/types.d.ts +2 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +2 -2
- package/dist/src/types.js.map +1 -1
- package/dist/tests/adapters-factory.test.d.ts +13 -0
- package/dist/tests/adapters-factory.test.d.ts.map +1 -0
- package/dist/tests/adapters-factory.test.js +308 -0
- package/dist/tests/adapters-factory.test.js.map +1 -0
- package/dist/tests/adapters-java.test.d.ts +13 -0
- package/dist/tests/adapters-java.test.d.ts.map +1 -0
- package/dist/tests/adapters-java.test.js +925 -0
- package/dist/tests/adapters-java.test.js.map +1 -0
- package/dist/tests/api/client.validation.test.d.ts +7 -0
- package/dist/tests/api/client.validation.test.d.ts.map +1 -0
- package/dist/tests/api/client.validation.test.js +183 -0
- package/dist/tests/api/client.validation.test.js.map +1 -0
- package/dist/tests/language-detector.test.d.ts +13 -0
- package/dist/tests/language-detector.test.d.ts.map +1 -0
- package/dist/tests/language-detector.test.js +674 -0
- package/dist/tests/language-detector.test.js.map +1 -0
- package/dist/tests/telemetry/posthog.test.d.ts +13 -0
- package/dist/tests/telemetry/posthog.test.d.ts.map +1 -0
- package/dist/tests/telemetry/posthog.test.js +600 -0
- package/dist/tests/telemetry/posthog.test.js.map +1 -0
- package/package.json +5 -6
- package/dist/src/security/RateLimiter.d.ts +0 -337
- package/dist/src/security/RateLimiter.d.ts.map +0 -1
- package/dist/src/security/RateLimiter.js +0 -782
- package/dist/src/security/RateLimiter.js.map +0 -1
- package/dist/src/security/scanner.d.ts +0 -151
- package/dist/src/security/scanner.d.ts.map +0 -1
- package/dist/src/security/scanner.js +0 -599
- package/dist/src/security/scanner.js.map +0 -1
|
@@ -0,0 +1,1059 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-1336: Multi-Language Analysis Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Integration tests for the complete multi-language analysis system,
|
|
5
|
+
* testing interactions between:
|
|
6
|
+
* - LanguageRouter with all adapters
|
|
7
|
+
* - ParseCache across languages
|
|
8
|
+
* - ParserWorkerPool with mixed language batches
|
|
9
|
+
* - ResultAggregator combining results from multiple languages
|
|
10
|
+
*
|
|
11
|
+
* @see docs/architecture/multi-language-analysis.md
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
14
|
+
import { LanguageRouter } from '../router.js';
|
|
15
|
+
import { ParseCache } from '../cache.js';
|
|
16
|
+
import { ParserWorkerPool } from '../worker-pool.js';
|
|
17
|
+
import { ResultAggregator } from '../aggregator.js';
|
|
18
|
+
import { TypeScriptAdapter } from '../adapters/typescript.js';
|
|
19
|
+
import { PythonAdapter } from '../adapters/python.js';
|
|
20
|
+
import { GoAdapter } from '../adapters/go.js';
|
|
21
|
+
import { RustAdapter } from '../adapters/rust.js';
|
|
22
|
+
import { JavaAdapter } from '../adapters/java.js';
|
|
23
|
+
// ============================================================
|
|
24
|
+
// Test Fixtures: Multi-Language Project Files
|
|
25
|
+
// ============================================================
|
|
26
|
+
const fixtures = {
|
|
27
|
+
typescript: {
|
|
28
|
+
path: 'src/api/handler.ts',
|
|
29
|
+
content: `
|
|
30
|
+
import { Request, Response } from 'express'
|
|
31
|
+
import type { User } from '../types'
|
|
32
|
+
import { validateUser } from '../validators'
|
|
33
|
+
|
|
34
|
+
export interface HandlerConfig {
|
|
35
|
+
timeout: number
|
|
36
|
+
maxRetries: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function handleUserRequest(req: Request, res: Response): Promise<void> {
|
|
40
|
+
const user = req.body as User
|
|
41
|
+
if (validateUser(user)) {
|
|
42
|
+
res.json({ success: true, user })
|
|
43
|
+
} else {
|
|
44
|
+
res.status(400).json({ error: 'Invalid user' })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const DEFAULT_CONFIG: HandlerConfig = {
|
|
49
|
+
timeout: 5000,
|
|
50
|
+
maxRetries: 3
|
|
51
|
+
}
|
|
52
|
+
`.trim(),
|
|
53
|
+
},
|
|
54
|
+
python: {
|
|
55
|
+
path: 'src/services/data_processor.py',
|
|
56
|
+
content: `
|
|
57
|
+
from typing import List, Dict, Optional
|
|
58
|
+
from dataclasses import dataclass
|
|
59
|
+
import pandas as pd
|
|
60
|
+
import numpy as np
|
|
61
|
+
from .utils import sanitize_input
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class DataConfig:
|
|
65
|
+
batch_size: int
|
|
66
|
+
max_workers: int
|
|
67
|
+
|
|
68
|
+
class DataProcessor:
|
|
69
|
+
def __init__(self, config: DataConfig):
|
|
70
|
+
self.config = config
|
|
71
|
+
self._cache: Dict[str, pd.DataFrame] = {}
|
|
72
|
+
|
|
73
|
+
async def process_batch(self, data: List[Dict]) -> pd.DataFrame:
|
|
74
|
+
df = pd.DataFrame(data)
|
|
75
|
+
return self._apply_transformations(df)
|
|
76
|
+
|
|
77
|
+
def _apply_transformations(self, df: pd.DataFrame) -> pd.DataFrame:
|
|
78
|
+
return df.dropna().reset_index(drop=True)
|
|
79
|
+
|
|
80
|
+
def create_processor(batch_size: int = 100) -> DataProcessor:
|
|
81
|
+
config = DataConfig(batch_size=batch_size, max_workers=4)
|
|
82
|
+
return DataProcessor(config)
|
|
83
|
+
`.trim(),
|
|
84
|
+
},
|
|
85
|
+
go: {
|
|
86
|
+
path: 'src/server/main.go',
|
|
87
|
+
content: `
|
|
88
|
+
package server
|
|
89
|
+
|
|
90
|
+
import (
|
|
91
|
+
"context"
|
|
92
|
+
"encoding/json"
|
|
93
|
+
"net/http"
|
|
94
|
+
|
|
95
|
+
"github.com/gin-gonic/gin"
|
|
96
|
+
"gorm.io/gorm"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
type Server struct {
|
|
100
|
+
router *gin.Engine
|
|
101
|
+
db *gorm.DB
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type UserRequest struct {
|
|
105
|
+
Name string \`json:"name"\`
|
|
106
|
+
Email string \`json:"email"\`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func NewServer(db *gorm.DB) *Server {
|
|
110
|
+
router := gin.Default()
|
|
111
|
+
return &Server{router: router, db: db}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func (s *Server) Start(addr string) error {
|
|
115
|
+
return s.router.Run(addr)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func (s *Server) handleGetUser(c *gin.Context) {
|
|
119
|
+
id := c.Param("id")
|
|
120
|
+
c.JSON(http.StatusOK, gin.H{"id": id})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func parseJSON(data []byte) (UserRequest, error) {
|
|
124
|
+
var req UserRequest
|
|
125
|
+
err := json.Unmarshal(data, &req)
|
|
126
|
+
return req, err
|
|
127
|
+
}
|
|
128
|
+
`.trim(),
|
|
129
|
+
},
|
|
130
|
+
rust: {
|
|
131
|
+
path: 'src/lib.rs',
|
|
132
|
+
content: `
|
|
133
|
+
use std::collections::HashMap;
|
|
134
|
+
use serde::{Deserialize, Serialize};
|
|
135
|
+
use tokio::sync::RwLock;
|
|
136
|
+
|
|
137
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
138
|
+
pub struct Config {
|
|
139
|
+
pub host: String,
|
|
140
|
+
pub port: u16,
|
|
141
|
+
pub max_connections: usize,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pub trait Handler: Send + Sync {
|
|
145
|
+
fn handle(&self, request: &Request) -> Response;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
pub struct Server {
|
|
149
|
+
config: Config,
|
|
150
|
+
handlers: HashMap<String, Box<dyn Handler>>,
|
|
151
|
+
connections: RwLock<Vec<Connection>>,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
impl Server {
|
|
155
|
+
pub fn new(config: Config) -> Self {
|
|
156
|
+
Server {
|
|
157
|
+
config,
|
|
158
|
+
handlers: HashMap::new(),
|
|
159
|
+
connections: RwLock::new(Vec::new()),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
pub async fn start(&self) -> Result<(), ServerError> {
|
|
164
|
+
println!("Starting server on {}:{}", self.config.host, self.config.port);
|
|
165
|
+
Ok(())
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fn register_handler(&mut self, path: String, handler: Box<dyn Handler>) {
|
|
169
|
+
self.handlers.insert(path, handler);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
pub fn create_default_config() -> Config {
|
|
174
|
+
Config {
|
|
175
|
+
host: "127.0.0.1".to_string(),
|
|
176
|
+
port: 8080,
|
|
177
|
+
max_connections: 100,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
`.trim(),
|
|
181
|
+
},
|
|
182
|
+
java: {
|
|
183
|
+
path: 'src/main/java/com/example/UserService.java',
|
|
184
|
+
content: `
|
|
185
|
+
package com.example;
|
|
186
|
+
|
|
187
|
+
import java.util.List;
|
|
188
|
+
import java.util.Optional;
|
|
189
|
+
import java.util.stream.Collectors;
|
|
190
|
+
|
|
191
|
+
import org.springframework.stereotype.Service;
|
|
192
|
+
import org.springframework.transaction.annotation.Transactional;
|
|
193
|
+
|
|
194
|
+
@Service
|
|
195
|
+
public class UserService {
|
|
196
|
+
private final UserRepository userRepository;
|
|
197
|
+
private final CacheService cacheService;
|
|
198
|
+
|
|
199
|
+
public UserService(UserRepository userRepository, CacheService cacheService) {
|
|
200
|
+
this.userRepository = userRepository;
|
|
201
|
+
this.cacheService = cacheService;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@Transactional(readOnly = true)
|
|
205
|
+
public Optional<User> findById(Long id) {
|
|
206
|
+
return userRepository.findById(id);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@Transactional
|
|
210
|
+
public User createUser(CreateUserRequest request) {
|
|
211
|
+
User user = new User(request.getName(), request.getEmail());
|
|
212
|
+
return userRepository.save(user);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public List<UserDTO> getAllUsers() {
|
|
216
|
+
return userRepository.findAll()
|
|
217
|
+
.stream()
|
|
218
|
+
.map(this::toDTO)
|
|
219
|
+
.collect(Collectors.toList());
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private UserDTO toDTO(User user) {
|
|
223
|
+
return new UserDTO(user.getId(), user.getName());
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
`.trim(),
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
// ============================================================
|
|
230
|
+
// LanguageRouter Integration Tests
|
|
231
|
+
// ============================================================
|
|
232
|
+
describe('SMI-1336: Multi-Language Integration Tests', () => {
|
|
233
|
+
describe('LanguageRouter with all adapters', () => {
|
|
234
|
+
let router;
|
|
235
|
+
beforeEach(() => {
|
|
236
|
+
router = new LanguageRouter();
|
|
237
|
+
router.registerAdapter(new TypeScriptAdapter());
|
|
238
|
+
router.registerAdapter(new PythonAdapter());
|
|
239
|
+
router.registerAdapter(new GoAdapter());
|
|
240
|
+
router.registerAdapter(new RustAdapter());
|
|
241
|
+
router.registerAdapter(new JavaAdapter());
|
|
242
|
+
});
|
|
243
|
+
afterEach(() => {
|
|
244
|
+
router.dispose();
|
|
245
|
+
});
|
|
246
|
+
it('registers all five language adapters', () => {
|
|
247
|
+
expect(router.adapterCount).toBe(5);
|
|
248
|
+
const languages = router.getSupportedLanguages();
|
|
249
|
+
expect(languages).toContain('typescript');
|
|
250
|
+
expect(languages).toContain('python');
|
|
251
|
+
expect(languages).toContain('go');
|
|
252
|
+
expect(languages).toContain('rust');
|
|
253
|
+
expect(languages).toContain('java');
|
|
254
|
+
});
|
|
255
|
+
it('routes files to correct adapters based on extension', () => {
|
|
256
|
+
// TypeScript/JavaScript
|
|
257
|
+
expect(router.getLanguage('file.ts')).toBe('typescript');
|
|
258
|
+
expect(router.getLanguage('file.tsx')).toBe('typescript');
|
|
259
|
+
expect(router.getLanguage('file.js')).toBe('typescript');
|
|
260
|
+
expect(router.getLanguage('file.jsx')).toBe('typescript');
|
|
261
|
+
// Python
|
|
262
|
+
expect(router.getLanguage('file.py')).toBe('python');
|
|
263
|
+
expect(router.getLanguage('file.pyi')).toBe('python');
|
|
264
|
+
// Go
|
|
265
|
+
expect(router.getLanguage('file.go')).toBe('go');
|
|
266
|
+
// Rust
|
|
267
|
+
expect(router.getLanguage('file.rs')).toBe('rust');
|
|
268
|
+
// Java
|
|
269
|
+
expect(router.getLanguage('file.java')).toBe('java');
|
|
270
|
+
});
|
|
271
|
+
it('returns null for unsupported extensions', () => {
|
|
272
|
+
expect(router.getLanguage('file.cpp')).toBeNull();
|
|
273
|
+
expect(router.getLanguage('file.rb')).toBeNull();
|
|
274
|
+
expect(router.getLanguage('file.php')).toBeNull();
|
|
275
|
+
});
|
|
276
|
+
it('canHandle returns correct values for all languages', () => {
|
|
277
|
+
expect(router.canHandle('src/main.ts')).toBe(true);
|
|
278
|
+
expect(router.canHandle('src/main.py')).toBe(true);
|
|
279
|
+
expect(router.canHandle('src/main.go')).toBe(true);
|
|
280
|
+
expect(router.canHandle('src/lib.rs')).toBe(true);
|
|
281
|
+
expect(router.canHandle('src/App.java')).toBe(true);
|
|
282
|
+
expect(router.canHandle('src/main.cpp')).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
it('parses TypeScript files correctly', () => {
|
|
285
|
+
const result = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
286
|
+
expect(result.imports.length).toBeGreaterThan(0);
|
|
287
|
+
expect(result.imports.some((i) => i.module === 'express')).toBe(true);
|
|
288
|
+
expect(result.functions.length).toBeGreaterThan(0);
|
|
289
|
+
expect(result.functions.some((f) => f.name === 'handleUserRequest')).toBe(true);
|
|
290
|
+
expect(result.exports.length).toBeGreaterThan(0);
|
|
291
|
+
expect(result.exports.some((e) => e.name === 'HandlerConfig')).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
it('parses Python files correctly', () => {
|
|
294
|
+
const result = router.parseFile(fixtures.python.content, fixtures.python.path);
|
|
295
|
+
expect(result.imports.length).toBeGreaterThan(0);
|
|
296
|
+
expect(result.imports.some((i) => i.module === 'pandas')).toBe(true);
|
|
297
|
+
expect(result.imports.some((i) => i.module === 'numpy')).toBe(true);
|
|
298
|
+
expect(result.functions.length).toBeGreaterThan(0);
|
|
299
|
+
expect(result.functions.some((f) => f.name === 'create_processor')).toBe(true);
|
|
300
|
+
expect(result.exports.length).toBeGreaterThan(0);
|
|
301
|
+
expect(result.exports.some((e) => e.name === 'DataProcessor')).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
it('parses Go files correctly', () => {
|
|
304
|
+
const result = router.parseFile(fixtures.go.content, fixtures.go.path);
|
|
305
|
+
expect(result.imports.length).toBeGreaterThan(0);
|
|
306
|
+
expect(result.imports.some((i) => i.module === 'github.com/gin-gonic/gin')).toBe(true);
|
|
307
|
+
expect(result.functions.length).toBeGreaterThan(0);
|
|
308
|
+
expect(result.functions.some((f) => f.name === 'NewServer')).toBe(true);
|
|
309
|
+
expect(result.exports.length).toBeGreaterThan(0);
|
|
310
|
+
expect(result.exports.some((e) => e.name === 'Server')).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
it('parses Rust files correctly', () => {
|
|
313
|
+
const result = router.parseFile(fixtures.rust.content, fixtures.rust.path);
|
|
314
|
+
expect(result.imports.length).toBeGreaterThan(0);
|
|
315
|
+
expect(result.imports.some((i) => i.module.includes('serde'))).toBe(true);
|
|
316
|
+
expect(result.functions.length).toBeGreaterThan(0);
|
|
317
|
+
expect(result.functions.some((f) => f.name === 'new')).toBe(true);
|
|
318
|
+
expect(result.exports.length).toBeGreaterThan(0);
|
|
319
|
+
expect(result.exports.some((e) => e.name === 'Config')).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
it('parses Java files correctly', () => {
|
|
322
|
+
const result = router.parseFile(fixtures.java.content, fixtures.java.path);
|
|
323
|
+
expect(result.imports.length).toBeGreaterThan(0);
|
|
324
|
+
expect(result.imports.some((i) => i.module.includes('springframework'))).toBe(true);
|
|
325
|
+
expect(result.functions.length).toBeGreaterThan(0);
|
|
326
|
+
expect(result.functions.some((f) => f.name === 'findById')).toBe(true);
|
|
327
|
+
expect(result.exports.length).toBeGreaterThan(0);
|
|
328
|
+
expect(result.exports.some((e) => e.name === 'UserService')).toBe(true);
|
|
329
|
+
});
|
|
330
|
+
it('aggregates framework rules from all adapters', () => {
|
|
331
|
+
const rules = router.getAllFrameworkRules();
|
|
332
|
+
// Should have rules from all languages
|
|
333
|
+
expect(rules.length).toBeGreaterThan(10);
|
|
334
|
+
// TypeScript frameworks
|
|
335
|
+
expect(rules.some((r) => r.name === 'React')).toBe(true);
|
|
336
|
+
expect(rules.some((r) => r.name === 'Express')).toBe(true);
|
|
337
|
+
// Python frameworks
|
|
338
|
+
expect(rules.some((r) => r.name === 'Django')).toBe(true);
|
|
339
|
+
expect(rules.some((r) => r.name === 'FastAPI')).toBe(true);
|
|
340
|
+
// Go frameworks
|
|
341
|
+
expect(rules.some((r) => r.name === 'Gin')).toBe(true);
|
|
342
|
+
// Rust frameworks
|
|
343
|
+
expect(rules.some((r) => r.name === 'Actix')).toBe(true);
|
|
344
|
+
// Java frameworks
|
|
345
|
+
expect(rules.some((r) => r.name === 'Spring')).toBe(true);
|
|
346
|
+
});
|
|
347
|
+
it('throws error for unsupported file when adapter not found', () => {
|
|
348
|
+
expect(() => router.parseFile('content', 'file.xyz')).toThrow(/No adapter registered/);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
// ============================================================
|
|
352
|
+
// ParseCache Integration Tests
|
|
353
|
+
// ============================================================
|
|
354
|
+
describe('ParseCache integration with adapters', () => {
|
|
355
|
+
let cache;
|
|
356
|
+
let router;
|
|
357
|
+
beforeEach(() => {
|
|
358
|
+
cache = new ParseCache({ maxMemoryMB: 50 });
|
|
359
|
+
router = new LanguageRouter();
|
|
360
|
+
router.registerAdapter(new TypeScriptAdapter());
|
|
361
|
+
router.registerAdapter(new PythonAdapter());
|
|
362
|
+
router.registerAdapter(new GoAdapter());
|
|
363
|
+
});
|
|
364
|
+
afterEach(() => {
|
|
365
|
+
cache.clear();
|
|
366
|
+
router.dispose();
|
|
367
|
+
});
|
|
368
|
+
it('caches parse results across different languages', () => {
|
|
369
|
+
// Parse and cache TypeScript
|
|
370
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
371
|
+
cache.set(fixtures.typescript.path, fixtures.typescript.content, tsResult);
|
|
372
|
+
// Parse and cache Python
|
|
373
|
+
const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
|
|
374
|
+
cache.set(fixtures.python.path, fixtures.python.content, pyResult);
|
|
375
|
+
// Parse and cache Go
|
|
376
|
+
const goResult = router.parseFile(fixtures.go.content, fixtures.go.path);
|
|
377
|
+
cache.set(fixtures.go.path, fixtures.go.content, goResult);
|
|
378
|
+
// Verify all cached
|
|
379
|
+
expect(cache.size).toBe(3);
|
|
380
|
+
expect(cache.has(fixtures.typescript.path)).toBe(true);
|
|
381
|
+
expect(cache.has(fixtures.python.path)).toBe(true);
|
|
382
|
+
expect(cache.has(fixtures.go.path)).toBe(true);
|
|
383
|
+
});
|
|
384
|
+
it('returns cached results on cache hit', () => {
|
|
385
|
+
const result = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
386
|
+
cache.set(fixtures.typescript.path, fixtures.typescript.content, result);
|
|
387
|
+
const cached = cache.get(fixtures.typescript.path, fixtures.typescript.content);
|
|
388
|
+
expect(cached).not.toBeNull();
|
|
389
|
+
expect(cached).toEqual(result);
|
|
390
|
+
});
|
|
391
|
+
it('invalidates cache on content change', () => {
|
|
392
|
+
const result = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
393
|
+
cache.set(fixtures.typescript.path, fixtures.typescript.content, result);
|
|
394
|
+
const modifiedContent = fixtures.typescript.content + '\n// new comment';
|
|
395
|
+
const cached = cache.get(fixtures.typescript.path, modifiedContent);
|
|
396
|
+
expect(cached).toBeNull();
|
|
397
|
+
});
|
|
398
|
+
it('tracks hit/miss statistics correctly', () => {
|
|
399
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
400
|
+
cache.set(fixtures.typescript.path, fixtures.typescript.content, tsResult);
|
|
401
|
+
// First access - hit
|
|
402
|
+
cache.get(fixtures.typescript.path, fixtures.typescript.content);
|
|
403
|
+
// Second access - hit
|
|
404
|
+
cache.get(fixtures.typescript.path, fixtures.typescript.content);
|
|
405
|
+
// Miss - file not cached
|
|
406
|
+
cache.get('not-cached.ts', 'content');
|
|
407
|
+
const stats = cache.getStats();
|
|
408
|
+
expect(stats.hitRate).toBeCloseTo(2 / 3, 2);
|
|
409
|
+
});
|
|
410
|
+
it('manages cache entries with add and invalidate', () => {
|
|
411
|
+
// Test cache management behavior with add and invalidate operations
|
|
412
|
+
const testCache = new ParseCache({ maxMemoryMB: 1 });
|
|
413
|
+
// Add multiple entries
|
|
414
|
+
for (let i = 0; i < 10; i++) {
|
|
415
|
+
const content = `export const x${i} = ${i}`;
|
|
416
|
+
const result = router.parseFile(content, `file${i}.ts`);
|
|
417
|
+
testCache.set(`file${i}.ts`, content, result);
|
|
418
|
+
}
|
|
419
|
+
// Verify entries were added
|
|
420
|
+
expect(testCache.size).toBe(10);
|
|
421
|
+
// Test that invalidation works (key cache behavior)
|
|
422
|
+
testCache.invalidate(['file0.ts', 'file1.ts', 'file2.ts']);
|
|
423
|
+
expect(testCache.size).toBe(7);
|
|
424
|
+
testCache.clear();
|
|
425
|
+
});
|
|
426
|
+
it('invalidates by pattern across languages', () => {
|
|
427
|
+
// Cache multiple files
|
|
428
|
+
const tsResult = router.parseFile(fixtures.typescript.content, 'src/api/handler.ts');
|
|
429
|
+
cache.set('src/api/handler.ts', fixtures.typescript.content, tsResult);
|
|
430
|
+
const pyResult = router.parseFile(fixtures.python.content, 'src/services/processor.py');
|
|
431
|
+
cache.set('src/services/processor.py', fixtures.python.content, pyResult);
|
|
432
|
+
const goResult = router.parseFile(fixtures.go.content, 'src/server/main.go');
|
|
433
|
+
cache.set('src/server/main.go', fixtures.go.content, goResult);
|
|
434
|
+
// Invalidate by pattern
|
|
435
|
+
cache.invalidatePattern('src/*.ts');
|
|
436
|
+
cache.invalidatePattern('src/*.py');
|
|
437
|
+
// Only Go file should remain
|
|
438
|
+
expect(cache.has('src/server/main.go')).toBe(true);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
// ============================================================
|
|
442
|
+
// ParserWorkerPool Integration Tests
|
|
443
|
+
// ============================================================
|
|
444
|
+
describe('ParserWorkerPool with mixed language batches', () => {
|
|
445
|
+
let pool;
|
|
446
|
+
beforeEach(() => {
|
|
447
|
+
pool = new ParserWorkerPool({
|
|
448
|
+
poolSize: 2,
|
|
449
|
+
minBatchForWorkers: 5, // Lower threshold for testing
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
afterEach(() => {
|
|
453
|
+
pool.dispose();
|
|
454
|
+
});
|
|
455
|
+
it('processes single-language batch', async () => {
|
|
456
|
+
const tasks = [
|
|
457
|
+
{
|
|
458
|
+
filePath: 'a.ts',
|
|
459
|
+
content: 'export const a = 1',
|
|
460
|
+
language: 'typescript',
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
filePath: 'b.ts',
|
|
464
|
+
content: 'export const b = 2',
|
|
465
|
+
language: 'typescript',
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
filePath: 'c.ts',
|
|
469
|
+
content: 'export function c() {}',
|
|
470
|
+
language: 'typescript',
|
|
471
|
+
},
|
|
472
|
+
];
|
|
473
|
+
const results = await pool.parseFiles(tasks);
|
|
474
|
+
expect(results).toHaveLength(3);
|
|
475
|
+
expect(results.every((r) => r.result !== undefined)).toBe(true);
|
|
476
|
+
expect(results.every((r) => r.error === undefined)).toBe(true);
|
|
477
|
+
});
|
|
478
|
+
it('processes mixed-language batch', async () => {
|
|
479
|
+
const tasks = [
|
|
480
|
+
{
|
|
481
|
+
filePath: 'handler.ts',
|
|
482
|
+
content: 'export function handle() {}',
|
|
483
|
+
language: 'typescript',
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
filePath: 'processor.py',
|
|
487
|
+
content: 'def process(): pass',
|
|
488
|
+
language: 'python',
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
filePath: 'main.go',
|
|
492
|
+
content: 'package main\nfunc main() {}',
|
|
493
|
+
language: 'go',
|
|
494
|
+
},
|
|
495
|
+
];
|
|
496
|
+
const results = await pool.parseFiles(tasks);
|
|
497
|
+
expect(results).toHaveLength(3);
|
|
498
|
+
const tsResult = results.find((r) => r.filePath === 'handler.ts');
|
|
499
|
+
const pyResult = results.find((r) => r.filePath === 'processor.py');
|
|
500
|
+
const goResult = results.find((r) => r.filePath === 'main.go');
|
|
501
|
+
expect(tsResult?.result.functions.length).toBeGreaterThan(0);
|
|
502
|
+
expect(pyResult?.result.functions.length).toBeGreaterThan(0);
|
|
503
|
+
expect(goResult?.result.functions.length).toBeGreaterThan(0);
|
|
504
|
+
});
|
|
505
|
+
it('handles large mixed batch with workers', async () => {
|
|
506
|
+
// Create a batch large enough to use workers
|
|
507
|
+
const tasks = [];
|
|
508
|
+
// Add TypeScript files
|
|
509
|
+
for (let i = 0; i < 5; i++) {
|
|
510
|
+
tasks.push({
|
|
511
|
+
filePath: `src/ts/file${i}.ts`,
|
|
512
|
+
content: `export const value${i} = ${i}\nexport function fn${i}() { return ${i} }`,
|
|
513
|
+
language: 'typescript',
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
// Add Python files
|
|
517
|
+
for (let i = 0; i < 5; i++) {
|
|
518
|
+
tasks.push({
|
|
519
|
+
filePath: `src/py/file${i}.py`,
|
|
520
|
+
content: `def func${i}(): return ${i}\nclass Class${i}: pass`,
|
|
521
|
+
language: 'python',
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
// Add Go files
|
|
525
|
+
for (let i = 0; i < 5; i++) {
|
|
526
|
+
tasks.push({
|
|
527
|
+
filePath: `src/go/file${i}.go`,
|
|
528
|
+
content: `package main\nfunc Func${i}() int { return ${i} }`,
|
|
529
|
+
language: 'go',
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
const results = await pool.parseFiles(tasks);
|
|
533
|
+
expect(results).toHaveLength(15);
|
|
534
|
+
expect(results.filter((r) => r.filePath.endsWith('.ts'))).toHaveLength(5);
|
|
535
|
+
expect(results.filter((r) => r.filePath.endsWith('.py'))).toHaveLength(5);
|
|
536
|
+
expect(results.filter((r) => r.filePath.endsWith('.go'))).toHaveLength(5);
|
|
537
|
+
});
|
|
538
|
+
it('handles empty batch gracefully', async () => {
|
|
539
|
+
const results = await pool.parseFiles([]);
|
|
540
|
+
expect(results).toHaveLength(0);
|
|
541
|
+
});
|
|
542
|
+
it('handles parse errors gracefully', async () => {
|
|
543
|
+
const tasks = [
|
|
544
|
+
{
|
|
545
|
+
filePath: 'valid.ts',
|
|
546
|
+
content: 'export const x = 1',
|
|
547
|
+
language: 'typescript',
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
filePath: 'unknown.xyz',
|
|
551
|
+
content: 'invalid content',
|
|
552
|
+
language: 'unknown',
|
|
553
|
+
},
|
|
554
|
+
];
|
|
555
|
+
const results = await pool.parseFiles(tasks);
|
|
556
|
+
expect(results).toHaveLength(2);
|
|
557
|
+
const validResult = results.find((r) => r.filePath === 'valid.ts');
|
|
558
|
+
expect(validResult?.error).toBeUndefined();
|
|
559
|
+
const invalidResult = results.find((r) => r.filePath === 'unknown.xyz');
|
|
560
|
+
expect(invalidResult?.error).toBeDefined();
|
|
561
|
+
});
|
|
562
|
+
it('reports timing information', async () => {
|
|
563
|
+
const tasks = [
|
|
564
|
+
{
|
|
565
|
+
filePath: 'test.ts',
|
|
566
|
+
content: fixtures.typescript.content,
|
|
567
|
+
language: 'typescript',
|
|
568
|
+
},
|
|
569
|
+
];
|
|
570
|
+
const results = await pool.parseFiles(tasks);
|
|
571
|
+
expect(results[0].durationMs).toBeGreaterThanOrEqual(0);
|
|
572
|
+
});
|
|
573
|
+
it('provides pool statistics', () => {
|
|
574
|
+
const stats = pool.getStats();
|
|
575
|
+
expect(stats.poolSize).toBe(2);
|
|
576
|
+
expect(stats.activeWorkers).toBe(0);
|
|
577
|
+
expect(stats.queuedTasks).toBe(0);
|
|
578
|
+
});
|
|
579
|
+
});
|
|
580
|
+
// ============================================================
|
|
581
|
+
// ResultAggregator Integration Tests
|
|
582
|
+
// ============================================================
|
|
583
|
+
describe('ResultAggregator combining results from multiple languages', () => {
|
|
584
|
+
let aggregator;
|
|
585
|
+
let router;
|
|
586
|
+
beforeEach(() => {
|
|
587
|
+
aggregator = new ResultAggregator();
|
|
588
|
+
router = new LanguageRouter();
|
|
589
|
+
router.registerAdapter(new TypeScriptAdapter());
|
|
590
|
+
router.registerAdapter(new PythonAdapter());
|
|
591
|
+
router.registerAdapter(new GoAdapter());
|
|
592
|
+
router.registerAdapter(new RustAdapter());
|
|
593
|
+
router.registerAdapter(new JavaAdapter());
|
|
594
|
+
});
|
|
595
|
+
afterEach(() => {
|
|
596
|
+
aggregator.reset();
|
|
597
|
+
router.dispose();
|
|
598
|
+
});
|
|
599
|
+
it('aggregates results from all supported languages', () => {
|
|
600
|
+
// Parse and aggregate TypeScript
|
|
601
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
602
|
+
aggregator.add({
|
|
603
|
+
filePath: fixtures.typescript.path,
|
|
604
|
+
language: 'typescript',
|
|
605
|
+
result: tsResult,
|
|
606
|
+
});
|
|
607
|
+
// Parse and aggregate Python
|
|
608
|
+
const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
|
|
609
|
+
aggregator.add({
|
|
610
|
+
filePath: fixtures.python.path,
|
|
611
|
+
language: 'python',
|
|
612
|
+
result: pyResult,
|
|
613
|
+
});
|
|
614
|
+
// Parse and aggregate Go
|
|
615
|
+
const goResult = router.parseFile(fixtures.go.content, fixtures.go.path);
|
|
616
|
+
aggregator.add({
|
|
617
|
+
filePath: fixtures.go.path,
|
|
618
|
+
language: 'go',
|
|
619
|
+
result: goResult,
|
|
620
|
+
});
|
|
621
|
+
// Parse and aggregate Rust
|
|
622
|
+
const rustResult = router.parseFile(fixtures.rust.content, fixtures.rust.path);
|
|
623
|
+
aggregator.add({
|
|
624
|
+
filePath: fixtures.rust.path,
|
|
625
|
+
language: 'rust',
|
|
626
|
+
result: rustResult,
|
|
627
|
+
});
|
|
628
|
+
// Parse and aggregate Java
|
|
629
|
+
const javaResult = router.parseFile(fixtures.java.content, fixtures.java.path);
|
|
630
|
+
aggregator.add({
|
|
631
|
+
filePath: fixtures.java.path,
|
|
632
|
+
language: 'java',
|
|
633
|
+
result: javaResult,
|
|
634
|
+
});
|
|
635
|
+
expect(aggregator.getFileCount()).toBe(5);
|
|
636
|
+
expect(aggregator.getLanguages()).toContain('typescript');
|
|
637
|
+
expect(aggregator.getLanguages()).toContain('python');
|
|
638
|
+
expect(aggregator.getLanguages()).toContain('go');
|
|
639
|
+
expect(aggregator.getLanguages()).toContain('rust');
|
|
640
|
+
expect(aggregator.getLanguages()).toContain('java');
|
|
641
|
+
});
|
|
642
|
+
it('aggregates imports from all languages with correct annotation', () => {
|
|
643
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
644
|
+
aggregator.add({
|
|
645
|
+
filePath: fixtures.typescript.path,
|
|
646
|
+
language: 'typescript',
|
|
647
|
+
result: tsResult,
|
|
648
|
+
});
|
|
649
|
+
const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
|
|
650
|
+
aggregator.add({
|
|
651
|
+
filePath: fixtures.python.path,
|
|
652
|
+
language: 'python',
|
|
653
|
+
result: pyResult,
|
|
654
|
+
});
|
|
655
|
+
const imports = aggregator.getImports();
|
|
656
|
+
// Should have imports from both languages
|
|
657
|
+
const tsImports = imports.filter((i) => i.language === 'typescript');
|
|
658
|
+
const pyImports = imports.filter((i) => i.language === 'python');
|
|
659
|
+
expect(tsImports.length).toBeGreaterThan(0);
|
|
660
|
+
expect(pyImports.length).toBeGreaterThan(0);
|
|
661
|
+
// Verify source file annotation
|
|
662
|
+
expect(tsImports.every((i) => i.sourceFile === fixtures.typescript.path)).toBe(true);
|
|
663
|
+
expect(pyImports.every((i) => i.sourceFile === fixtures.python.path)).toBe(true);
|
|
664
|
+
});
|
|
665
|
+
it('aggregates exports from all languages with correct kind', () => {
|
|
666
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
667
|
+
aggregator.add({
|
|
668
|
+
filePath: fixtures.typescript.path,
|
|
669
|
+
language: 'typescript',
|
|
670
|
+
result: tsResult,
|
|
671
|
+
});
|
|
672
|
+
const goResult = router.parseFile(fixtures.go.content, fixtures.go.path);
|
|
673
|
+
aggregator.add({
|
|
674
|
+
filePath: fixtures.go.path,
|
|
675
|
+
language: 'go',
|
|
676
|
+
result: goResult,
|
|
677
|
+
});
|
|
678
|
+
const exports = aggregator.getExports();
|
|
679
|
+
// TypeScript exports
|
|
680
|
+
expect(exports.some((e) => e.name === 'HandlerConfig' && e.kind === 'interface')).toBe(true);
|
|
681
|
+
// Go exports (structs)
|
|
682
|
+
expect(exports.some((e) => e.name === 'Server' && e.kind === 'struct')).toBe(true);
|
|
683
|
+
});
|
|
684
|
+
it('aggregates functions from all languages with async detection', () => {
|
|
685
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
686
|
+
aggregator.add({
|
|
687
|
+
filePath: fixtures.typescript.path,
|
|
688
|
+
language: 'typescript',
|
|
689
|
+
result: tsResult,
|
|
690
|
+
});
|
|
691
|
+
const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
|
|
692
|
+
aggregator.add({
|
|
693
|
+
filePath: fixtures.python.path,
|
|
694
|
+
language: 'python',
|
|
695
|
+
result: pyResult,
|
|
696
|
+
});
|
|
697
|
+
const functions = aggregator.getFunctions();
|
|
698
|
+
// TypeScript async function
|
|
699
|
+
const tsAsyncFunc = functions.find((f) => f.name === 'handleUserRequest');
|
|
700
|
+
expect(tsAsyncFunc?.isAsync).toBe(true);
|
|
701
|
+
expect(tsAsyncFunc?.language).toBe('typescript');
|
|
702
|
+
// Python async function
|
|
703
|
+
const pyAsyncFunc = functions.find((f) => f.name === 'process_batch');
|
|
704
|
+
expect(pyAsyncFunc?.isAsync).toBe(true);
|
|
705
|
+
expect(pyAsyncFunc?.language).toBe('python');
|
|
706
|
+
});
|
|
707
|
+
it('builds complete CodebaseContext from multi-language project', () => {
|
|
708
|
+
// Add all fixture files
|
|
709
|
+
const files = [
|
|
710
|
+
{ ...fixtures.typescript, language: 'typescript' },
|
|
711
|
+
{ ...fixtures.python, language: 'python' },
|
|
712
|
+
{ ...fixtures.go, language: 'go' },
|
|
713
|
+
{ ...fixtures.rust, language: 'rust' },
|
|
714
|
+
{ ...fixtures.java, language: 'java' },
|
|
715
|
+
];
|
|
716
|
+
for (const file of files) {
|
|
717
|
+
const result = router.parseFile(file.content, file.path);
|
|
718
|
+
aggregator.add({
|
|
719
|
+
filePath: file.path,
|
|
720
|
+
language: file.language,
|
|
721
|
+
result,
|
|
722
|
+
});
|
|
723
|
+
aggregator.addLines(file.content.split('\n').length);
|
|
724
|
+
}
|
|
725
|
+
const context = aggregator.build('/project', [
|
|
726
|
+
{ name: 'express', version: '^4.18.0', isDev: false },
|
|
727
|
+
{ name: 'pandas', version: '>=2.0', isDev: false },
|
|
728
|
+
], [
|
|
729
|
+
{ name: 'Express', confidence: 0.9, evidence: ['import express'] },
|
|
730
|
+
{ name: 'Gin', confidence: 0.9, evidence: ['import gin'] },
|
|
731
|
+
], { durationMs: 500, version: '2.0.0', cacheHitRate: 0.8 });
|
|
732
|
+
expect(context.rootPath).toBe('/project');
|
|
733
|
+
expect(context.imports.length).toBeGreaterThan(0);
|
|
734
|
+
expect(context.exports.length).toBeGreaterThan(0);
|
|
735
|
+
expect(context.functions.length).toBeGreaterThan(0);
|
|
736
|
+
expect(context.frameworks).toHaveLength(2);
|
|
737
|
+
expect(context.dependencies).toHaveLength(2);
|
|
738
|
+
expect(context.stats.totalFiles).toBe(5);
|
|
739
|
+
expect(context.stats.totalLines).toBeGreaterThan(0);
|
|
740
|
+
expect(context.metadata.languages).toHaveLength(5);
|
|
741
|
+
expect(context.metadata.cacheHitRate).toBe(0.8);
|
|
742
|
+
});
|
|
743
|
+
it('provides accurate summary statistics', () => {
|
|
744
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
745
|
+
aggregator.add({
|
|
746
|
+
filePath: fixtures.typescript.path,
|
|
747
|
+
language: 'typescript',
|
|
748
|
+
result: tsResult,
|
|
749
|
+
});
|
|
750
|
+
aggregator.addLines(fixtures.typescript.content.split('\n').length);
|
|
751
|
+
const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
|
|
752
|
+
aggregator.add({
|
|
753
|
+
filePath: fixtures.python.path,
|
|
754
|
+
language: 'python',
|
|
755
|
+
result: pyResult,
|
|
756
|
+
});
|
|
757
|
+
aggregator.addLines(fixtures.python.content.split('\n').length);
|
|
758
|
+
const summary = aggregator.getSummary();
|
|
759
|
+
expect(summary.files).toBe(2);
|
|
760
|
+
expect(summary.imports).toBeGreaterThan(0);
|
|
761
|
+
expect(summary.exports).toBeGreaterThan(0);
|
|
762
|
+
expect(summary.functions).toBeGreaterThan(0);
|
|
763
|
+
expect(summary.lines).toBeGreaterThan(0);
|
|
764
|
+
expect(summary.languages).toContain('typescript');
|
|
765
|
+
expect(summary.languages).toContain('python');
|
|
766
|
+
});
|
|
767
|
+
it('merges two aggregators correctly', () => {
|
|
768
|
+
const aggregator1 = new ResultAggregator();
|
|
769
|
+
const aggregator2 = new ResultAggregator();
|
|
770
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
771
|
+
aggregator1.add({
|
|
772
|
+
filePath: fixtures.typescript.path,
|
|
773
|
+
language: 'typescript',
|
|
774
|
+
result: tsResult,
|
|
775
|
+
});
|
|
776
|
+
const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
|
|
777
|
+
aggregator2.add({
|
|
778
|
+
filePath: fixtures.python.path,
|
|
779
|
+
language: 'python',
|
|
780
|
+
result: pyResult,
|
|
781
|
+
});
|
|
782
|
+
aggregator1.merge(aggregator2);
|
|
783
|
+
expect(aggregator1.getFileCount()).toBe(2);
|
|
784
|
+
expect(aggregator1.getLanguages()).toContain('typescript');
|
|
785
|
+
expect(aggregator1.getLanguages()).toContain('python');
|
|
786
|
+
});
|
|
787
|
+
it('resets state completely', () => {
|
|
788
|
+
const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
789
|
+
aggregator.add({
|
|
790
|
+
filePath: fixtures.typescript.path,
|
|
791
|
+
language: 'typescript',
|
|
792
|
+
result: tsResult,
|
|
793
|
+
});
|
|
794
|
+
aggregator.reset();
|
|
795
|
+
expect(aggregator.getFileCount()).toBe(0);
|
|
796
|
+
expect(aggregator.getImports()).toHaveLength(0);
|
|
797
|
+
expect(aggregator.getExports()).toHaveLength(0);
|
|
798
|
+
expect(aggregator.getFunctions()).toHaveLength(0);
|
|
799
|
+
expect(aggregator.getLanguages()).toHaveLength(0);
|
|
800
|
+
});
|
|
801
|
+
});
|
|
802
|
+
// ============================================================
|
|
803
|
+
// End-to-End Multi-Language Project Scenario
|
|
804
|
+
// ============================================================
|
|
805
|
+
describe('End-to-end multi-language project analysis', () => {
|
|
806
|
+
let router;
|
|
807
|
+
let cache;
|
|
808
|
+
let aggregator;
|
|
809
|
+
beforeEach(() => {
|
|
810
|
+
router = new LanguageRouter();
|
|
811
|
+
router.registerAdapter(new TypeScriptAdapter());
|
|
812
|
+
router.registerAdapter(new PythonAdapter());
|
|
813
|
+
router.registerAdapter(new GoAdapter());
|
|
814
|
+
cache = new ParseCache({ maxMemoryMB: 50 });
|
|
815
|
+
aggregator = new ResultAggregator();
|
|
816
|
+
});
|
|
817
|
+
afterEach(() => {
|
|
818
|
+
router.dispose();
|
|
819
|
+
cache.clear();
|
|
820
|
+
aggregator.reset();
|
|
821
|
+
});
|
|
822
|
+
it('analyzes a realistic mixed-language microservices project', () => {
|
|
823
|
+
// Simulate a microservices project with multiple languages
|
|
824
|
+
const projectFiles = [
|
|
825
|
+
{
|
|
826
|
+
path: 'api-gateway/src/server.ts',
|
|
827
|
+
content: `
|
|
828
|
+
import express from 'express'
|
|
829
|
+
import { createProxyMiddleware } from 'http-proxy-middleware'
|
|
830
|
+
|
|
831
|
+
export function createGateway(config: GatewayConfig) {
|
|
832
|
+
const app = express()
|
|
833
|
+
app.use('/users', createProxyMiddleware({ target: config.userServiceUrl }))
|
|
834
|
+
return app
|
|
835
|
+
}
|
|
836
|
+
`.trim(),
|
|
837
|
+
language: 'typescript',
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
path: 'user-service/app/main.py',
|
|
841
|
+
content: `
|
|
842
|
+
from fastapi import FastAPI, Depends
|
|
843
|
+
from sqlalchemy.orm import Session
|
|
844
|
+
from .database import get_db
|
|
845
|
+
from .models import User
|
|
846
|
+
|
|
847
|
+
app = FastAPI()
|
|
848
|
+
|
|
849
|
+
@app.get("/users/{user_id}")
|
|
850
|
+
async def get_user(user_id: int, db: Session = Depends(get_db)):
|
|
851
|
+
return db.query(User).filter(User.id == user_id).first()
|
|
852
|
+
`.trim(),
|
|
853
|
+
language: 'python',
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
path: 'notification-service/main.go',
|
|
857
|
+
content: `
|
|
858
|
+
package main
|
|
859
|
+
|
|
860
|
+
import (
|
|
861
|
+
"github.com/gin-gonic/gin"
|
|
862
|
+
"github.com/go-redis/redis/v8"
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
func main() {
|
|
866
|
+
router := gin.Default()
|
|
867
|
+
router.POST("/notify", HandleNotify)
|
|
868
|
+
router.Run(":8082")
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
func HandleNotify(c *gin.Context) {
|
|
872
|
+
c.JSON(200, gin.H{"status": "sent"})
|
|
873
|
+
}
|
|
874
|
+
`.trim(),
|
|
875
|
+
language: 'go',
|
|
876
|
+
},
|
|
877
|
+
];
|
|
878
|
+
// Parse each file with caching
|
|
879
|
+
for (const file of projectFiles) {
|
|
880
|
+
// Check cache first
|
|
881
|
+
let result = cache.get(file.path, file.content);
|
|
882
|
+
if (!result) {
|
|
883
|
+
// Parse if not cached
|
|
884
|
+
result = router.parseFile(file.content, file.path);
|
|
885
|
+
cache.set(file.path, file.content, result);
|
|
886
|
+
}
|
|
887
|
+
// Aggregate
|
|
888
|
+
aggregator.add({
|
|
889
|
+
filePath: file.path,
|
|
890
|
+
language: file.language,
|
|
891
|
+
result,
|
|
892
|
+
});
|
|
893
|
+
aggregator.addLines(file.content.split('\n').length);
|
|
894
|
+
}
|
|
895
|
+
// Verify comprehensive analysis
|
|
896
|
+
const context = aggregator.build('/microservices-project', [], [], {
|
|
897
|
+
durationMs: 100,
|
|
898
|
+
version: '2.0.0',
|
|
899
|
+
cacheHitRate: 0,
|
|
900
|
+
});
|
|
901
|
+
// File counts
|
|
902
|
+
expect(context.stats.totalFiles).toBe(3);
|
|
903
|
+
expect(context.metadata.languages).toHaveLength(3);
|
|
904
|
+
// Cross-language imports
|
|
905
|
+
const imports = context.imports;
|
|
906
|
+
expect(imports.some((i) => i.module === 'express' && i.language === 'typescript')).toBe(true);
|
|
907
|
+
expect(imports.some((i) => i.module === 'fastapi' && i.language === 'python')).toBe(true);
|
|
908
|
+
expect(imports.some((i) => i.module === 'github.com/gin-gonic/gin' && i.language === 'go')).toBe(true);
|
|
909
|
+
// Cross-language functions
|
|
910
|
+
const functions = context.functions;
|
|
911
|
+
expect(functions.some((f) => f.name === 'createGateway' && f.language === 'typescript')).toBe(true);
|
|
912
|
+
expect(functions.some((f) => f.name === 'get_user' && f.language === 'python')).toBe(true);
|
|
913
|
+
expect(functions.some((f) => f.name === 'HandleNotify' && f.language === 'go')).toBe(true);
|
|
914
|
+
});
|
|
915
|
+
it('verifies cross-adapter output format consistency', () => {
|
|
916
|
+
// All adapters should produce consistent ParseResult structure
|
|
917
|
+
const testCases = [
|
|
918
|
+
{ path: 'test.ts', content: 'export function test() {}' },
|
|
919
|
+
{ path: 'test.py', content: 'def test(): pass' },
|
|
920
|
+
{ path: 'test.go', content: 'package main\nfunc Test() {}' },
|
|
921
|
+
];
|
|
922
|
+
for (const { path, content } of testCases) {
|
|
923
|
+
const result = router.parseFile(content, path);
|
|
924
|
+
// All results should have the required arrays
|
|
925
|
+
expect(Array.isArray(result.imports)).toBe(true);
|
|
926
|
+
expect(Array.isArray(result.exports)).toBe(true);
|
|
927
|
+
expect(Array.isArray(result.functions)).toBe(true);
|
|
928
|
+
// Functions should have consistent shape
|
|
929
|
+
for (const func of result.functions) {
|
|
930
|
+
expect(typeof func.name).toBe('string');
|
|
931
|
+
expect(typeof func.parameterCount).toBe('number');
|
|
932
|
+
expect(typeof func.isAsync).toBe('boolean');
|
|
933
|
+
expect(typeof func.isExported).toBe('boolean');
|
|
934
|
+
expect(typeof func.sourceFile).toBe('string');
|
|
935
|
+
expect(typeof func.line).toBe('number');
|
|
936
|
+
}
|
|
937
|
+
// Imports should have consistent shape
|
|
938
|
+
for (const imp of result.imports) {
|
|
939
|
+
expect(typeof imp.module).toBe('string');
|
|
940
|
+
expect(Array.isArray(imp.namedImports)).toBe(true);
|
|
941
|
+
expect(typeof imp.isTypeOnly).toBe('boolean');
|
|
942
|
+
expect(typeof imp.sourceFile).toBe('string');
|
|
943
|
+
}
|
|
944
|
+
// Exports should have consistent shape
|
|
945
|
+
for (const exp of result.exports) {
|
|
946
|
+
expect(typeof exp.name).toBe('string');
|
|
947
|
+
expect(typeof exp.kind).toBe('string');
|
|
948
|
+
expect(typeof exp.isDefault).toBe('boolean');
|
|
949
|
+
expect(typeof exp.sourceFile).toBe('string');
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
it('tests cache hit/miss behavior across languages', () => {
|
|
954
|
+
const files = [
|
|
955
|
+
{ path: 'a.ts', content: 'export const a = 1' },
|
|
956
|
+
{ path: 'b.py', content: 'a = 1' },
|
|
957
|
+
{ path: 'c.go', content: 'package main\nvar a = 1' },
|
|
958
|
+
];
|
|
959
|
+
// First pass - all misses
|
|
960
|
+
for (const file of files) {
|
|
961
|
+
const cached = cache.get(file.path, file.content);
|
|
962
|
+
expect(cached).toBeNull();
|
|
963
|
+
const result = router.parseFile(file.content, file.path);
|
|
964
|
+
cache.set(file.path, file.content, result);
|
|
965
|
+
}
|
|
966
|
+
// Second pass - all hits
|
|
967
|
+
for (const file of files) {
|
|
968
|
+
const cached = cache.get(file.path, file.content);
|
|
969
|
+
expect(cached).not.toBeNull();
|
|
970
|
+
}
|
|
971
|
+
const stats = cache.getStats();
|
|
972
|
+
// 3 misses + 3 hits = 50% hit rate
|
|
973
|
+
expect(stats.hitRate).toBeCloseTo(0.5, 2);
|
|
974
|
+
// Third pass with modified content - misses
|
|
975
|
+
for (const file of files) {
|
|
976
|
+
const modifiedContent = file.content + '\n// modified';
|
|
977
|
+
const cached = cache.get(file.path, modifiedContent);
|
|
978
|
+
expect(cached).toBeNull();
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
});
|
|
982
|
+
// ============================================================
|
|
983
|
+
// Performance Tests
|
|
984
|
+
// ============================================================
|
|
985
|
+
describe('Performance benchmarks', () => {
|
|
986
|
+
let router;
|
|
987
|
+
let pool;
|
|
988
|
+
beforeEach(() => {
|
|
989
|
+
router = new LanguageRouter();
|
|
990
|
+
router.registerAdapter(new TypeScriptAdapter());
|
|
991
|
+
router.registerAdapter(new PythonAdapter());
|
|
992
|
+
router.registerAdapter(new GoAdapter());
|
|
993
|
+
pool = new ParserWorkerPool({ poolSize: 4, minBatchForWorkers: 5 });
|
|
994
|
+
});
|
|
995
|
+
afterEach(() => {
|
|
996
|
+
router.dispose();
|
|
997
|
+
pool.dispose();
|
|
998
|
+
});
|
|
999
|
+
it('parses mixed-language batch under 500ms', async () => {
|
|
1000
|
+
const tasks = [];
|
|
1001
|
+
// Generate test files
|
|
1002
|
+
for (let i = 0; i < 10; i++) {
|
|
1003
|
+
tasks.push({
|
|
1004
|
+
filePath: `file${i}.ts`,
|
|
1005
|
+
content: fixtures.typescript.content,
|
|
1006
|
+
language: 'typescript',
|
|
1007
|
+
});
|
|
1008
|
+
tasks.push({
|
|
1009
|
+
filePath: `file${i}.py`,
|
|
1010
|
+
content: fixtures.python.content,
|
|
1011
|
+
language: 'python',
|
|
1012
|
+
});
|
|
1013
|
+
tasks.push({
|
|
1014
|
+
filePath: `file${i}.go`,
|
|
1015
|
+
content: fixtures.go.content,
|
|
1016
|
+
language: 'go',
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
const start = performance.now();
|
|
1020
|
+
const results = await pool.parseFiles(tasks);
|
|
1021
|
+
const duration = performance.now() - start;
|
|
1022
|
+
expect(results).toHaveLength(30);
|
|
1023
|
+
expect(duration).toBeLessThan(500);
|
|
1024
|
+
});
|
|
1025
|
+
it('maintains consistent parsing speed across languages', () => {
|
|
1026
|
+
const iterations = 10;
|
|
1027
|
+
const times = {
|
|
1028
|
+
typescript: [],
|
|
1029
|
+
python: [],
|
|
1030
|
+
go: [],
|
|
1031
|
+
};
|
|
1032
|
+
for (let i = 0; i < iterations; i++) {
|
|
1033
|
+
// TypeScript
|
|
1034
|
+
let start = performance.now();
|
|
1035
|
+
router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
|
|
1036
|
+
times.typescript.push(performance.now() - start);
|
|
1037
|
+
// Python
|
|
1038
|
+
start = performance.now();
|
|
1039
|
+
router.parseFile(fixtures.python.content, fixtures.python.path);
|
|
1040
|
+
times.python.push(performance.now() - start);
|
|
1041
|
+
// Go
|
|
1042
|
+
start = performance.now();
|
|
1043
|
+
router.parseFile(fixtures.go.content, fixtures.go.path);
|
|
1044
|
+
times.go.push(performance.now() - start);
|
|
1045
|
+
}
|
|
1046
|
+
// Calculate averages
|
|
1047
|
+
const avgTimes = {
|
|
1048
|
+
typescript: times.typescript.reduce((a, b) => a + b, 0) / iterations,
|
|
1049
|
+
python: times.python.reduce((a, b) => a + b, 0) / iterations,
|
|
1050
|
+
go: times.go.reduce((a, b) => a + b, 0) / iterations,
|
|
1051
|
+
};
|
|
1052
|
+
// All languages should parse in under 50ms average
|
|
1053
|
+
expect(avgTimes.typescript).toBeLessThan(50);
|
|
1054
|
+
expect(avgTimes.python).toBeLessThan(50);
|
|
1055
|
+
expect(avgTimes.go).toBeLessThan(50);
|
|
1056
|
+
});
|
|
1057
|
+
});
|
|
1058
|
+
});
|
|
1059
|
+
//# sourceMappingURL=integration.test.js.map
|