ciyidogan commited on
Commit
bdc8c47
·
verified ·
1 Parent(s): 0e360c0

Update flare-ui/src/app/components/test/test.component.ts

Browse files
flare-ui/src/app/components/test/test.component.ts CHANGED
@@ -1,339 +1,419 @@
1
- import { Component, inject } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
3
- import { FormsModule } from '@angular/forms';
4
- import { ApiService } from '../../services/api.service';
5
-
6
- interface TestResult {
7
- name: string;
8
- status: 'PASS' | 'FAIL' | 'RUNNING';
9
- duration_ms?: number;
10
- error?: string;
11
- }
12
-
13
- interface TestRun {
14
- test_type: string;
15
- start_time: string;
16
- tests: TestResult[];
17
- summary: {
18
- total: number;
19
- passed: number;
20
- failed: number;
21
- duration_ms: number;
22
- };
23
- }
24
-
25
- @Component({
26
- selector: 'app-test',
27
- standalone: true,
28
- imports: [CommonModule, FormsModule],
29
- template: `
30
- <div class="test-container">
31
- <h2>System Tests</h2>
32
-
33
- <div class="test-controls">
34
- <button class="btn btn-primary" (click)="runAllTests()" [disabled]="running">
35
- Run All Tests
36
- </button>
37
- <button class="btn btn-secondary" (click)="runSelectedTests()" [disabled]="running || selectedTests.length === 0">
38
- Run Selected
39
- </button>
40
- <button class="btn btn-danger" (click)="stopTests()" [disabled]="!running">
41
- Stop
42
- </button>
43
- </div>
44
-
45
- <div class="test-categories">
46
- <div class="category">
47
- <label>
48
- <input type="checkbox" [(ngModel)]="allSelected" (change)="toggleAll()">
49
- <strong>All Tests</strong>
50
- </label>
51
- </div>
52
- <div class="category">
53
- <label>
54
- <input type="checkbox" [(ngModel)]="categories.ui" (change)="updateSelection()">
55
- UI Tests (15 tests)
56
- </label>
57
- <div class="sub-tests" *ngIf="categories.ui">
58
- <label><input type="checkbox"> Login Flow</label>
59
- <label><input type="checkbox"> Project CRUD</label>
60
- <label><input type="checkbox"> Version Management</label>
61
- </div>
62
- </div>
63
- <div class="category">
64
- <label>
65
- <input type="checkbox" [(ngModel)]="categories.backend" (change)="updateSelection()">
66
- Backend Tests (22 tests)
67
- </label>
68
- <div class="sub-tests" *ngIf="categories.backend">
69
- <label><input type="checkbox"> Authentication</label>
70
- <label><input type="checkbox"> API Endpoints</label>
71
- <label><input type="checkbox"> Race Conditions</label>
72
- </div>
73
- </div>
74
- <div class="category">
75
- <label>
76
- <input type="checkbox" [(ngModel)]="categories.integration" (change)="updateSelection()">
77
- Integration Tests (18 tests)
78
- </label>
79
- </div>
80
- <div class="category">
81
- <label>
82
- <input type="checkbox" [(ngModel)]="categories.spark" (change)="updateSelection()">
83
- Spark Tests (8 tests)
84
- </label>
85
- </div>
86
- </div>
87
-
88
- @if (currentRun) {
89
- <div class="test-results">
90
- <h3>Test Results:</h3>
91
- <div class="results-list">
92
- @for (test of currentRun.tests; track test.name) {
93
- <div class="test-result" [class.pass]="test.status === 'PASS'" [class.fail]="test.status === 'FAIL'">
94
- @if (test.status === 'PASS') {
95
- <span class="status">✓</span>
96
- } @else if (test.status === 'FAIL') {
97
- <span class="status">✗</span>
98
- } @else {
99
- <span class="status spinner"></span>
100
- }
101
- <span class="name">{{ test.name }}</span>
102
- @if (test.duration_ms) {
103
- <span class="duration">{{ test.duration_ms }}ms</span>
104
- }
105
- @if (test.error) {
106
- <div class="error">{{ test.error }}</div>
107
- }
108
- </div>
109
- }
110
- </div>
111
-
112
- @if (!running && currentRun.summary) {
113
- <div class="test-summary">
114
- <div class="progress-bar">
115
- <div
116
- class="progress-fill"
117
- [style.width.%]="(currentRun.summary.passed / currentRun.summary.total) * 100"
118
- [class.success]="currentRun.summary.failed === 0"
119
- [class.warning]="currentRun.summary.failed > 0"
120
- ></div>
121
- </div>
122
- <div class="summary-text">
123
- Progress: {{ currentRun.summary.passed + currentRun.summary.failed }}/{{ currentRun.summary.total }}
124
- ({{ ((currentRun.summary.passed / currentRun.summary.total) * 100).toFixed(0) }}%)
125
- </div>
126
- <div class="summary-stats">
127
- Passed: {{ currentRun.summary.passed }} |
128
- Failed: {{ currentRun.summary.failed }} |
129
- Total time: {{ (currentRun.summary.duration_ms / 1000).toFixed(1) }}s
130
- </div>
131
- </div>
132
- }
133
- </div>
134
- }
135
-
136
- @if (!currentRun && !running) {
137
- <div class="empty-state">
138
- <p>No test results yet. Click "Run All Tests" to start.</p>
139
- </div>
140
- }
141
- </div>
142
- `,
143
- styles: [`
144
- .test-container {
145
- h2 {
146
- margin-bottom: 1.5rem;
147
- }
148
- }
149
-
150
- .test-controls {
151
- display: flex;
152
- gap: 0.5rem;
153
- margin-bottom: 1.5rem;
154
- }
155
-
156
- .test-categories {
157
- background: white;
158
- border: 1px solid #dee2e6;
159
- border-radius: 0.25rem;
160
- padding: 1rem;
161
- margin-bottom: 1.5rem;
162
-
163
- .category {
164
- margin-bottom: 0.5rem;
165
-
166
- label {
167
- display: flex;
168
- align-items: center;
169
- gap: 0.5rem;
170
- cursor: pointer;
171
-
172
- input[type="checkbox"] {
173
- cursor: pointer;
174
- }
175
- }
176
- }
177
-
178
- .sub-tests {
179
- margin-left: 1.5rem;
180
- margin-top: 0.5rem;
181
-
182
- label {
183
- display: block;
184
- margin-bottom: 0.25rem;
185
- font-weight: normal;
186
- color: #6c757d;
187
- }
188
- }
189
- }
190
-
191
- .test-results {
192
- background: white;
193
- border: 1px solid #dee2e6;
194
- border-radius: 0.25rem;
195
- padding: 1rem;
196
-
197
- h3 {
198
- margin-top: 0;
199
- margin-bottom: 1rem;
200
- }
201
-
202
- .results-list {
203
- max-height: 400px;
204
- overflow-y: auto;
205
- margin-bottom: 1rem;
206
- }
207
-
208
- .test-result {
209
- display: flex;
210
- align-items: center;
211
- gap: 0.5rem;
212
- padding: 0.5rem;
213
- border-bottom: 1px solid #f0f0f0;
214
-
215
- &.pass .status { color: #28a745; }
216
- &.fail .status { color: #dc3545; }
217
-
218
- .status {
219
- width: 20px;
220
- text-align: center;
221
- }
222
-
223
- .name {
224
- flex: 1;
225
- }
226
-
227
- .duration {
228
- color: #6c757d;
229
- font-size: 0.875rem;
230
- }
231
-
232
- .error {
233
- width: 100%;
234
- margin-top: 0.5rem;
235
- padding: 0.5rem;
236
- background-color: #f8d7da;
237
- color: #721c24;
238
- border-radius: 0.25rem;
239
- font-size: 0.875rem;
240
- }
241
- }
242
- }
243
-
244
- .test-summary {
245
- border-top: 1px solid #dee2e6;
246
- padding-top: 1rem;
247
-
248
- .progress-bar {
249
- height: 20px;
250
- background-color: #e9ecef;
251
- border-radius: 0.25rem;
252
- overflow: hidden;
253
- margin-bottom: 0.5rem;
254
-
255
- .progress-fill {
256
- height: 100%;
257
- transition: width 0.3s ease;
258
-
259
- &.success { background-color: #28a745; }
260
- &.warning { background-color: #ffc107; }
261
- }
262
- }
263
-
264
- .summary-text, .summary-stats {
265
- text-align: center;
266
- color: #6c757d;
267
- margin-bottom: 0.5rem;
268
- }
269
- }
270
-
271
- .empty-state {
272
- text-align: center;
273
- padding: 3rem;
274
- background-color: white;
275
- border-radius: 0.25rem;
276
-
277
- p {
278
- color: #6c757d;
279
- }
280
- }
281
- `]
282
- })
283
- export class TestComponent {
284
- private apiService = inject(ApiService);
285
-
286
- running = false;
287
- allSelected = false;
288
- categories = {
289
- ui: false,
290
- backend: false,
291
- integration: false,
292
- spark: false
293
- };
294
- selectedTests: string[] = [];
295
- currentRun: TestRun | null = null;
296
-
297
- toggleAll() {
298
- this.categories.ui = this.allSelected;
299
- this.categories.backend = this.allSelected;
300
- this.categories.integration = this.allSelected;
301
- this.categories.spark = this.allSelected;
302
- this.updateSelection();
303
- }
304
-
305
- updateSelection() {
306
- this.selectedTests = [];
307
- if (this.categories.ui) this.selectedTests.push('ui');
308
- if (this.categories.backend) this.selectedTests.push('backend');
309
- if (this.categories.integration) this.selectedTests.push('integration');
310
- if (this.categories.spark) this.selectedTests.push('spark');
311
-
312
- this.allSelected = this.selectedTests.length === 4;
313
- }
314
-
315
- runAllTests() {
316
- this.running = true;
317
- this.apiService.runTests('all').subscribe({
318
- next: (result) => {
319
- this.currentRun = result;
320
- this.running = false;
321
- },
322
- error: (err) => {
323
- console.error('Test run failed:', err);
324
- this.running = false;
325
- }
326
- });
327
- }
328
-
329
- runSelectedTests() {
330
- // TODO: Implement selected tests
331
- console.log('Running selected tests:', this.selectedTests);
332
- this.runAllTests(); // For now, just run all
333
- }
334
-
335
- stopTests() {
336
- this.running = false;
337
- // TODO: Implement stop functionality
338
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  }
 
1
+ import { Component, inject, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
5
+ import { MatCheckboxModule } from '@angular/material/checkbox';
6
+ import { MatButtonModule } from '@angular/material/button';
7
+ import { MatIconModule } from '@angular/material/icon';
8
+ import { MatExpansionModule } from '@angular/material/expansion';
9
+ import { MatListModule } from '@angular/material/list';
10
+ import { MatChipsModule } from '@angular/material/chips';
11
+ import { ApiService } from '../../services/api.service';
12
+ import { HttpClient } from '@angular/common/http';
13
+
14
+ interface TestResult {
15
+ name: string;
16
+ status: 'PASS' | 'FAIL' | 'RUNNING' | 'SKIPPED';
17
+ duration_ms?: number;
18
+ error?: string;
19
+ details?: string;
20
+ }
21
+
22
+ interface TestCategory {
23
+ name: string;
24
+ displayName: string;
25
+ tests: TestCase[];
26
+ selected: boolean;
27
+ expanded: boolean;
28
+ }
29
+
30
+ interface TestCase {
31
+ name: string;
32
+ category: string;
33
+ selected: boolean;
34
+ testFn: () => Promise<TestResult>;
35
+ }
36
+
37
+ @Component({
38
+ selector: 'app-test',
39
+ standalone: true,
40
+ imports: [
41
+ CommonModule,
42
+ FormsModule,
43
+ MatProgressBarModule,
44
+ MatCheckboxModule,
45
+ MatButtonModule,
46
+ MatIconModule,
47
+ MatExpansionModule,
48
+ MatListModule,
49
+ MatChipsModule
50
+ ],
51
+ templateUrl: './test.component.html',
52
+ styleUrls: ['./test.component.scss']
53
+ })
54
+ export class TestComponent implements OnInit {
55
+ private apiService = inject(ApiService);
56
+ private http = inject(HttpClient);
57
+
58
+ running = false;
59
+ currentTest: string = '';
60
+ testResults: TestResult[] = [];
61
+
62
+ categories: TestCategory[] = [
63
+ {
64
+ name: 'auth',
65
+ displayName: 'Authentication Tests',
66
+ tests: [],
67
+ selected: false,
68
+ expanded: false
69
+ },
70
+ {
71
+ name: 'api',
72
+ displayName: 'API Endpoint Tests',
73
+ tests: [],
74
+ selected: false,
75
+ expanded: false
76
+ },
77
+ {
78
+ name: 'validation',
79
+ displayName: 'Validation Tests',
80
+ tests: [],
81
+ selected: false,
82
+ expanded: false
83
+ },
84
+ {
85
+ name: 'integration',
86
+ displayName: 'Integration Tests',
87
+ tests: [],
88
+ selected: false,
89
+ expanded: false
90
+ }
91
+ ];
92
+
93
+ get allSelected(): boolean {
94
+ return this.categories.every(c => c.selected);
95
+ }
96
+
97
+ get selectedTests(): TestCase[] {
98
+ return this.categories
99
+ .filter(c => c.selected)
100
+ .flatMap(c => c.tests);
101
+ }
102
+
103
+ get totalTests(): number {
104
+ return this.categories.reduce((sum, c) => sum + c.tests.length, 0);
105
+ }
106
+
107
+ get passedTests(): number {
108
+ return this.testResults.filter(r => r.status === 'PASS').length;
109
+ }
110
+
111
+ get failedTests(): number {
112
+ return this.testResults.filter(r => r.status === 'FAIL').length;
113
+ }
114
+
115
+ get progress(): number {
116
+ if (this.testResults.length === 0) return 0;
117
+ return (this.testResults.length / this.selectedTests.length) * 100;
118
+ }
119
+
120
+ ngOnInit() {
121
+ this.initializeTests();
122
+ }
123
+
124
+ initializeTests() {
125
+ // Authentication Tests
126
+ this.addTest('auth', 'Login with valid credentials', async () => {
127
+ try {
128
+ const response = await this.http.post('/api/login', {
129
+ username: 'admin',
130
+ password: 'admin'
131
+ }).toPromise() as any;
132
+
133
+ return {
134
+ name: 'Login with valid credentials',
135
+ status: response.token ? 'PASS' : 'FAIL',
136
+ duration_ms: 120
137
+ };
138
+ } catch (error) {
139
+ return {
140
+ name: 'Login with valid credentials',
141
+ status: 'FAIL',
142
+ error: 'Login failed',
143
+ duration_ms: 120
144
+ };
145
+ }
146
+ });
147
+
148
+ this.addTest('auth', 'Login with invalid credentials', async () => {
149
+ try {
150
+ await this.http.post('/api/login', {
151
+ username: 'admin',
152
+ password: 'wrong'
153
+ }).toPromise();
154
+
155
+ return {
156
+ name: 'Login with invalid credentials',
157
+ status: 'FAIL',
158
+ error: 'Expected 401 but got success',
159
+ duration_ms: 80
160
+ };
161
+ } catch (error: any) {
162
+ return {
163
+ name: 'Login with invalid credentials',
164
+ status: error.status === 401 ? 'PASS' : 'FAIL',
165
+ duration_ms: 80,
166
+ details: 'Correctly rejected invalid credentials'
167
+ };
168
+ }
169
+ });
170
+
171
+ // API Endpoint Tests
172
+ this.addTest('api', 'GET /api/environment', async () => {
173
+ const start = Date.now();
174
+ try {
175
+ const response = await this.apiService.getEnvironment().toPromise();
176
+ return {
177
+ name: 'GET /api/environment',
178
+ status: response.work_mode ? 'PASS' : 'FAIL',
179
+ duration_ms: Date.now() - start
180
+ };
181
+ } catch (error) {
182
+ return {
183
+ name: 'GET /api/environment',
184
+ status: 'FAIL',
185
+ error: 'Failed to get environment',
186
+ duration_ms: Date.now() - start
187
+ };
188
+ }
189
+ });
190
+
191
+ this.addTest('api', 'GET /api/projects', async () => {
192
+ const start = Date.now();
193
+ try {
194
+ const response = await this.apiService.getProjects().toPromise();
195
+ return {
196
+ name: 'GET /api/projects',
197
+ status: Array.isArray(response) ? 'PASS' : 'FAIL',
198
+ duration_ms: Date.now() - start,
199
+ details: `Retrieved ${response.length} projects`
200
+ };
201
+ } catch (error) {
202
+ return {
203
+ name: 'GET /api/projects',
204
+ status: 'FAIL',
205
+ error: 'Failed to get projects',
206
+ duration_ms: Date.now() - start
207
+ };
208
+ }
209
+ });
210
+
211
+ this.addTest('api', 'GET /api/apis', async () => {
212
+ const start = Date.now();
213
+ try {
214
+ const response = await this.apiService.getAPIs().toPromise();
215
+ return {
216
+ name: 'GET /api/apis',
217
+ status: Array.isArray(response) ? 'PASS' : 'FAIL',
218
+ duration_ms: Date.now() - start,
219
+ details: `Retrieved ${response.length} APIs`
220
+ };
221
+ } catch (error) {
222
+ return {
223
+ name: 'GET /api/apis',
224
+ status: 'FAIL',
225
+ error: 'Failed to get APIs',
226
+ duration_ms: Date.now() - start
227
+ };
228
+ }
229
+ });
230
+
231
+ // Validation Tests
232
+ this.addTest('validation', 'Regex validation - valid pattern', async () => {
233
+ const start = Date.now();
234
+ try {
235
+ const response = await this.apiService.validateRegex('^[A-Z]{3}$', 'ABC').toPromise();
236
+ return {
237
+ name: 'Regex validation - valid pattern',
238
+ status: response.valid && response.matches ? 'PASS' : 'FAIL',
239
+ duration_ms: Date.now() - start
240
+ };
241
+ } catch (error) {
242
+ return {
243
+ name: 'Regex validation - valid pattern',
244
+ status: 'FAIL',
245
+ error: 'Validation failed',
246
+ duration_ms: Date.now() - start
247
+ };
248
+ }
249
+ });
250
+
251
+ this.addTest('validation', 'Regex validation - invalid pattern', async () => {
252
+ const start = Date.now();
253
+ try {
254
+ const response = await this.apiService.validateRegex('[invalid', 'test').toPromise();
255
+ return {
256
+ name: 'Regex validation - invalid pattern',
257
+ status: !response.valid ? 'PASS' : 'FAIL',
258
+ duration_ms: Date.now() - start,
259
+ details: 'Correctly identified invalid regex'
260
+ };
261
+ } catch (error) {
262
+ return {
263
+ name: 'Regex validation - invalid pattern',
264
+ status: 'PASS',
265
+ duration_ms: Date.now() - start,
266
+ details: 'Correctly rejected invalid regex'
267
+ };
268
+ }
269
+ });
270
+
271
+ // Integration Tests
272
+ this.addTest('integration', 'Create and delete project', async () => {
273
+ const start = Date.now();
274
+ let projectId: number | null = null;
275
+
276
+ try {
277
+ // Create project
278
+ const createResponse = await this.apiService.createProject({
279
+ name: `test_project_${Date.now()}`,
280
+ caption: 'Test Project'
281
+ }).toPromise() as any;
282
+
283
+ if (!createResponse.id) {
284
+ throw new Error('Project creation failed');
285
+ }
286
+
287
+ projectId = createResponse.id;
288
+
289
+ // Delete project
290
+ await this.apiService.deleteProject(projectId).toPromise();
291
+
292
+ return {
293
+ name: 'Create and delete project',
294
+ status: 'PASS',
295
+ duration_ms: Date.now() - start,
296
+ details: 'Project lifecycle test passed'
297
+ };
298
+ } catch (error: any) {
299
+ // Try to clean up if project was created
300
+ if (projectId) {
301
+ try {
302
+ await this.apiService.deleteProject(projectId).toPromise();
303
+ } catch {}
304
+ }
305
+
306
+ return {
307
+ name: 'Create and delete project',
308
+ status: 'FAIL',
309
+ error: error.message || 'Test failed',
310
+ duration_ms: Date.now() - start
311
+ };
312
+ }
313
+ });
314
+
315
+ this.addTest('integration', 'API used in intent cannot be deleted', async () => {
316
+ const start = Date.now();
317
+ try {
318
+ // Try to delete an API that's used in intents
319
+ await this.apiService.deleteAPI('book_flight_api').toPromise();
320
+
321
+ return {
322
+ name: 'API used in intent cannot be deleted',
323
+ status: 'FAIL',
324
+ error: 'Expected error but API was deleted',
325
+ duration_ms: Date.now() - start
326
+ };
327
+ } catch (error: any) {
328
+ return {
329
+ name: 'API used in intent cannot be deleted',
330
+ status: error.status === 400 ? 'PASS' : 'FAIL',
331
+ duration_ms: Date.now() - start,
332
+ details: 'Correctly prevented deletion of API in use'
333
+ };
334
+ }
335
+ });
336
+
337
+ // Update test counts
338
+ this.categories.forEach(cat => {
339
+ cat.displayName = `${cat.displayName} (${cat.tests.length} tests)`;
340
+ });
341
+ }
342
+
343
+ private addTest(category: string, name: string, testFn: () => Promise<TestResult>) {
344
+ const cat = this.categories.find(c => c.name === category);
345
+ if (cat) {
346
+ cat.tests.push({
347
+ name,
348
+ category,
349
+ selected: true,
350
+ testFn
351
+ });
352
+ }
353
+ }
354
+
355
+ toggleAll() {
356
+ this.categories.forEach(c => c.selected = this.allSelected);
357
+ }
358
+
359
+ async runAllTests() {
360
+ this.categories.forEach(c => c.selected = true);
361
+ await this.runTests();
362
+ }
363
+
364
+ async runSelectedTests() {
365
+ await this.runTests();
366
+ }
367
+
368
+ async runTests() {
369
+ if (this.running) return;
370
+
371
+ this.running = true;
372
+ this.testResults = [];
373
+ this.currentTest = '';
374
+
375
+ const testsToRun = this.selectedTests;
376
+
377
+ for (const test of testsToRun) {
378
+ this.currentTest = test.name;
379
+
380
+ try {
381
+ const result = await test.testFn();
382
+ this.testResults.push(result);
383
+ } catch (error: any) {
384
+ this.testResults.push({
385
+ name: test.name,
386
+ status: 'FAIL',
387
+ error: error.message || 'Unexpected error'
388
+ });
389
+ }
390
+
391
+ // Small delay between tests
392
+ await new Promise(resolve => setTimeout(resolve, 100));
393
+ }
394
+
395
+ this.running = false;
396
+ this.currentTest = '';
397
+ }
398
+
399
+ stopTests() {
400
+ this.running = false;
401
+ this.currentTest = '';
402
+ }
403
+
404
+ getTestResult(testName: string): TestResult | undefined {
405
+ return this.testResults.find(r => r.name === testName);
406
+ }
407
+
408
+ getCategoryResults(category: TestCategory): { passed: number; failed: number; total: number } {
409
+ const categoryResults = this.testResults.filter(r =>
410
+ category.tests.some(t => t.name === r.name)
411
+ );
412
+
413
+ return {
414
+ passed: categoryResults.filter(r => r.status === 'PASS').length,
415
+ failed: categoryResults.filter(r => r.status === 'FAIL').length,
416
+ total: category.tests.length
417
+ };
418
+ }
419
  }