faf-taf-git 2.0.4
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/.faf +126 -0
- package/.faf-dna +95 -0
- package/.fafignore +41 -0
- package/.taf +21 -0
- package/CHANGELOG.md +208 -0
- package/CLAUDE.md +30 -0
- package/CONTRIBUTING.md +286 -0
- package/LICENSE +21 -0
- package/README.md +496 -0
- package/action.yml +42 -0
- package/dist/bundle/index.js +36554 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +380 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36560 -0
- package/dist/index.js.map +1 -0
- package/dist/licenses.txt +148 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +147 -0
- package/dist/main.js.map +1 -0
- package/dist/parsers/jest.d.ts +38 -0
- package/dist/parsers/jest.d.ts.map +1 -0
- package/dist/parsers/jest.js +87 -0
- package/dist/parsers/jest.js.map +1 -0
- package/dist/sourcemap-register.js +1 -0
- package/dist/taf-core.d.ts +12 -0
- package/dist/taf-core.d.ts.map +1 -0
- package/dist/taf-core.js +136 -0
- package/dist/taf-core.js.map +1 -0
- package/docs/RELEASE-NOTES.md +200 -0
- package/docs/USER-GUIDE.md +301 -0
- package/jest.config.js +13 -0
- package/package.json +47 -0
- package/project.faf +259 -0
- package/src/cli.ts +393 -0
- package/src/index.ts +131 -0
- package/src/parsers/jest.ts +104 -0
- package/src/taf-core.ts +139 -0
- package/test-mcp.ts +83 -0
- package/test-standalone.ts +87 -0
- package/tests/jest-parser.test.ts +122 -0
- package/tests/wjttc-taf.test.ts +470 -0
- package/tsconfig.json +18 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* faf-taf-git v2.0.0 - GitHub Actions wrapper
|
|
3
|
+
*
|
|
4
|
+
* Pre-Capture Pattern: Reads test output from file (written by previous step)
|
|
5
|
+
* Handles GitHub Actions-specific inputs/outputs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as core from '@actions/core';
|
|
9
|
+
import * as exec from '@actions/exec';
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { parseJestOutput } from './parsers/jest';
|
|
13
|
+
import { updateTafFile } from './taf-core';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Commit .taf file changes to git
|
|
17
|
+
*/
|
|
18
|
+
async function commitTafUpdate(cwd: string, message: string): Promise<void> {
|
|
19
|
+
const execOptions = { cwd };
|
|
20
|
+
|
|
21
|
+
// Configure git
|
|
22
|
+
await exec.exec('git', ['config', '--global', 'user.name', 'faf-taf-git[bot]'], execOptions);
|
|
23
|
+
await exec.exec('git', ['config', '--global', 'user.email', 'faf-taf-git[bot]@users.noreply.github.com'], execOptions);
|
|
24
|
+
|
|
25
|
+
// Stage .taf file
|
|
26
|
+
await exec.exec('git', ['add', '.taf'], execOptions);
|
|
27
|
+
|
|
28
|
+
// Check if there are changes to commit
|
|
29
|
+
const { exitCode } = await exec.getExecOutput('git', ['diff', '--cached', '--name-only'], execOptions);
|
|
30
|
+
|
|
31
|
+
if (exitCode === 0) {
|
|
32
|
+
await exec.exec('git', ['commit', '-m', message], execOptions);
|
|
33
|
+
|
|
34
|
+
// Try to push (might fail in some environments, that's ok)
|
|
35
|
+
try {
|
|
36
|
+
await exec.exec('git', ['push'], execOptions);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
core.warning('Could not push changes (this is ok in some CI environments)');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function run(): Promise<void> {
|
|
44
|
+
try {
|
|
45
|
+
// Get GitHub Action inputs
|
|
46
|
+
const testOutputFile = core.getInput('test-output-file', { required: true });
|
|
47
|
+
const autoCommit = core.getInput('auto-commit') !== 'false';
|
|
48
|
+
const commitMessage = core.getInput('commit-message') || 'chore(taf): update .taf receipt [skip ci]';
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
|
|
51
|
+
core.info(`Reading test output from: ${testOutputFile}`);
|
|
52
|
+
|
|
53
|
+
// Read test output file (written by previous CI step)
|
|
54
|
+
const testOutputPath = path.isAbsolute(testOutputFile)
|
|
55
|
+
? testOutputFile
|
|
56
|
+
: path.join(cwd, testOutputFile);
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(testOutputPath)) {
|
|
59
|
+
core.setFailed(`Test output file not found: ${testOutputPath}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const testOutput = fs.readFileSync(testOutputPath, 'utf-8');
|
|
64
|
+
core.info(`Read ${testOutput.length} bytes from test output file`);
|
|
65
|
+
|
|
66
|
+
// Debug: Show sample of what we're parsing
|
|
67
|
+
const lines = testOutput.split('\n');
|
|
68
|
+
const testSummaryLine = lines.find(line => line.includes('Tests:'));
|
|
69
|
+
if (testSummaryLine) {
|
|
70
|
+
core.info(`Found summary line: ${testSummaryLine.substring(0, 200)}`);
|
|
71
|
+
} else {
|
|
72
|
+
core.warning('No "Tests:" summary line found in output');
|
|
73
|
+
// Show last 20 lines to help debug
|
|
74
|
+
core.info('Last 20 lines of output:');
|
|
75
|
+
lines.slice(-20).forEach((line, i) => {
|
|
76
|
+
core.info(` ${i}: ${line.substring(0, 150)}`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Parse test results
|
|
81
|
+
const testResults = parseJestOutput(testOutput);
|
|
82
|
+
|
|
83
|
+
if (!testResults) {
|
|
84
|
+
core.setFailed('Could not parse test output. Ensure you are using Jest.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
core.info(`Parsed results: ${testResults.passed}/${testResults.total} tests passing`);
|
|
89
|
+
|
|
90
|
+
// Set GitHub Action outputs
|
|
91
|
+
core.setOutput('result', testResults.result);
|
|
92
|
+
core.setOutput('passed', testResults.passed.toString());
|
|
93
|
+
core.setOutput('failed', testResults.failed.toString());
|
|
94
|
+
core.setOutput('total', testResults.total.toString());
|
|
95
|
+
|
|
96
|
+
// Check if .taf file exists
|
|
97
|
+
const tafPath = path.join(cwd, '.taf');
|
|
98
|
+
if (!fs.existsSync(tafPath)) {
|
|
99
|
+
core.setFailed('No .taf file found. Run `faf taf init` to create one.');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Update .taf file
|
|
104
|
+
const updated = await updateTafFile(tafPath, testResults);
|
|
105
|
+
|
|
106
|
+
if (!updated) {
|
|
107
|
+
core.setOutput('taf-updated', 'false');
|
|
108
|
+
core.setFailed('.taf file was not updated');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
core.setOutput('taf-updated', 'true');
|
|
113
|
+
core.info('✅ .taf file updated successfully');
|
|
114
|
+
|
|
115
|
+
// Commit changes if enabled
|
|
116
|
+
if (autoCommit) {
|
|
117
|
+
await commitTafUpdate(cwd, commitMessage);
|
|
118
|
+
core.info('✅ Changes committed to git');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof Error) {
|
|
123
|
+
core.setFailed(error.message);
|
|
124
|
+
} else {
|
|
125
|
+
core.setFailed('Unknown error occurred');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Run the GitHub Action
|
|
131
|
+
run();
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest output parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts test counts from Jest CLI output
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface TestResults {
|
|
8
|
+
total: number;
|
|
9
|
+
passed: number;
|
|
10
|
+
failed: number;
|
|
11
|
+
skipped?: number;
|
|
12
|
+
result: 'PASSED' | 'FAILED' | 'IMPROVED' | 'DEGRADED';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse Jest output to extract test results
|
|
17
|
+
*
|
|
18
|
+
* Jest output formats:
|
|
19
|
+
* - "Tests: 1 failed, 172 passed, 173 total"
|
|
20
|
+
* - "Tests: 173 passed, 173 total"
|
|
21
|
+
* - "Test Suites: 2 failed, 16 passed, 18 total"
|
|
22
|
+
* - "Tests: 9 skipped, 799 passed, 808 total" (with padding)
|
|
23
|
+
*/
|
|
24
|
+
export function parseJestOutput(output: string): TestResults | null {
|
|
25
|
+
// Strip ANSI color codes and other control characters
|
|
26
|
+
// eslint-disable-next-line no-control-regex
|
|
27
|
+
let cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
|
28
|
+
// Also strip carriage returns and normalize line endings
|
|
29
|
+
cleanOutput = cleanOutput.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
30
|
+
|
|
31
|
+
// Find the Jest test summary line
|
|
32
|
+
// CRITICAL: Must match "Tests:" line that contains "total" to avoid matching
|
|
33
|
+
// test descriptions like "Brake Tests: Critical dogfooding scenarios"
|
|
34
|
+
const testSummaryPattern = /Tests:\s+(.+?\d+\s+total)/im;
|
|
35
|
+
const testLineMatch = cleanOutput.match(testSummaryPattern);
|
|
36
|
+
|
|
37
|
+
if (!testLineMatch || !testLineMatch[1]) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const testLine = testLineMatch[1];
|
|
42
|
+
|
|
43
|
+
// Extract counts using flexible patterns
|
|
44
|
+
let total = 0;
|
|
45
|
+
let passed = 0;
|
|
46
|
+
let failed = 0;
|
|
47
|
+
let skipped = 0;
|
|
48
|
+
|
|
49
|
+
// Parse numbers - use global search on the whole output near the Tests: line
|
|
50
|
+
const totalMatch = testLine.match(/(\d+)\s+total/i);
|
|
51
|
+
if (totalMatch) {
|
|
52
|
+
total = parseInt(totalMatch[1], 10);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const passedMatch = testLine.match(/(\d+)\s+passed/i);
|
|
56
|
+
if (passedMatch) {
|
|
57
|
+
passed = parseInt(passedMatch[1], 10);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const failedMatch = testLine.match(/(\d+)\s+failed/i);
|
|
61
|
+
if (failedMatch) {
|
|
62
|
+
failed = parseInt(failedMatch[1], 10);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const skippedMatch = testLine.match(/(\d+)\s+skipped/i);
|
|
66
|
+
if (skippedMatch) {
|
|
67
|
+
skipped = parseInt(skippedMatch[1], 10);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate
|
|
71
|
+
if (total === 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Determine result
|
|
76
|
+
let result: 'PASSED' | 'FAILED' | 'IMPROVED' | 'DEGRADED' = 'PASSED';
|
|
77
|
+
if (failed > 0) {
|
|
78
|
+
result = 'FAILED';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
total,
|
|
83
|
+
passed,
|
|
84
|
+
failed,
|
|
85
|
+
skipped: skipped > 0 ? skipped : undefined,
|
|
86
|
+
result,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Example Jest outputs for testing:
|
|
92
|
+
*
|
|
93
|
+
* All passing:
|
|
94
|
+
* "Tests: 173 passed, 173 total"
|
|
95
|
+
*
|
|
96
|
+
* Some failing:
|
|
97
|
+
* "Tests: 1 failed, 172 passed, 173 total"
|
|
98
|
+
*
|
|
99
|
+
* With skipped:
|
|
100
|
+
* "Tests: 1 failed, 2 skipped, 170 passed, 173 total"
|
|
101
|
+
*
|
|
102
|
+
* With padding (CI):
|
|
103
|
+
* "Tests: 9 skipped, 799 passed, 808 total"
|
|
104
|
+
*/
|
package/src/taf-core.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAF Core - Simplified for GitHub Action
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for reading, parsing, and updating .taf files
|
|
5
|
+
* Derived from faf-cli TAF implementation (MCP-portable)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as yaml from 'yaml';
|
|
10
|
+
import { TestResults } from './parsers/jest';
|
|
11
|
+
|
|
12
|
+
interface TAFFile {
|
|
13
|
+
format_version?: string;
|
|
14
|
+
project?: string;
|
|
15
|
+
created?: string;
|
|
16
|
+
last_updated?: string;
|
|
17
|
+
test_history: TestRun[];
|
|
18
|
+
[key: string]: any; // Allow other fields
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface TestRun {
|
|
22
|
+
timestamp: string;
|
|
23
|
+
result: 'PASSED' | 'FAILED' | 'IMPROVED' | 'DEGRADED';
|
|
24
|
+
tests: {
|
|
25
|
+
total: number;
|
|
26
|
+
passed: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
skipped?: number;
|
|
29
|
+
};
|
|
30
|
+
command?: string;
|
|
31
|
+
trigger?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Update .taf file with new test results
|
|
36
|
+
* Creates the file if it doesn't exist
|
|
37
|
+
*/
|
|
38
|
+
export function updateTafFile(
|
|
39
|
+
tafPath: string,
|
|
40
|
+
testResults: TestResults
|
|
41
|
+
): boolean {
|
|
42
|
+
try {
|
|
43
|
+
let taf: TAFFile;
|
|
44
|
+
|
|
45
|
+
// Check if .taf exists, create if not
|
|
46
|
+
if (fs.existsSync(tafPath)) {
|
|
47
|
+
const content = fs.readFileSync(tafPath, 'utf-8');
|
|
48
|
+
taf = parseTAF(content);
|
|
49
|
+
} else {
|
|
50
|
+
// Create new .taf file
|
|
51
|
+
const projectName = require('path').basename(require('path').dirname(tafPath));
|
|
52
|
+
taf = createTAF(projectName);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Create test run entry
|
|
56
|
+
const run: TestRun = {
|
|
57
|
+
timestamp: new Date().toISOString(),
|
|
58
|
+
result: testResults.result,
|
|
59
|
+
tests: {
|
|
60
|
+
total: testResults.total,
|
|
61
|
+
passed: testResults.passed,
|
|
62
|
+
failed: testResults.failed,
|
|
63
|
+
skipped: testResults.skipped,
|
|
64
|
+
},
|
|
65
|
+
trigger: 'github-actions',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Append to history
|
|
69
|
+
taf.test_history.push(run);
|
|
70
|
+
|
|
71
|
+
// Serialize and write back
|
|
72
|
+
const updated = serializeTAF(taf);
|
|
73
|
+
fs.writeFileSync(tafPath, updated, 'utf-8');
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Failed to update .taf file:', error);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a new TAF file structure
|
|
84
|
+
*/
|
|
85
|
+
function createTAF(projectName: string): TAFFile {
|
|
86
|
+
return {
|
|
87
|
+
format_version: '1.0.0',
|
|
88
|
+
project: projectName,
|
|
89
|
+
created: new Date().toISOString(),
|
|
90
|
+
last_updated: new Date().toISOString(),
|
|
91
|
+
test_history: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Parse .taf file content
|
|
97
|
+
*/
|
|
98
|
+
function parseTAF(content: string): TAFFile {
|
|
99
|
+
// Split on document separator and take only first document
|
|
100
|
+
const lines = content.split('\n');
|
|
101
|
+
const yamlLines: string[] = [];
|
|
102
|
+
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
// Stop at document separator (but skip initial one if present)
|
|
105
|
+
if (line.trim() === '---' && yamlLines.length > 0) {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
// Skip initial document separator
|
|
109
|
+
if (line.trim() === '---' && yamlLines.length === 0) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
yamlLines.push(line);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const parsed = yaml.parse(yamlLines.join('\n'));
|
|
116
|
+
|
|
117
|
+
// Basic validation
|
|
118
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
119
|
+
throw new Error('Invalid .taf file: not a valid YAML object');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!parsed.test_history || !Array.isArray(parsed.test_history)) {
|
|
123
|
+
throw new Error('Invalid .taf file: missing or invalid test_history');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return parsed as TAFFile;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Serialize TAF object back to YAML
|
|
131
|
+
* Uses yaml library to maintain existing format
|
|
132
|
+
*/
|
|
133
|
+
function serializeTAF(taf: TAFFile): string {
|
|
134
|
+
// Update last_updated timestamp
|
|
135
|
+
taf.last_updated = new Date().toISOString();
|
|
136
|
+
|
|
137
|
+
// Use yaml.stringify to preserve format
|
|
138
|
+
return yaml.stringify(taf);
|
|
139
|
+
}
|
package/test-mcp.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test action with claude-faf-mcp repository
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { parseJestOutput } from './src/parsers/jest';
|
|
8
|
+
import { updateTafFile } from './src/taf-core';
|
|
9
|
+
|
|
10
|
+
async function testMCP() {
|
|
11
|
+
console.log('Testing action with claude-faf-mcp repo...\n');
|
|
12
|
+
|
|
13
|
+
// Simulate Jest output (based on typical MCP test output)
|
|
14
|
+
const jestOutput = `
|
|
15
|
+
PASS tests/tools.test.ts
|
|
16
|
+
PASS tests/validation.test.ts
|
|
17
|
+
|
|
18
|
+
Test Suites: 2 passed, 2 total
|
|
19
|
+
Tests: 15 passed, 15 total
|
|
20
|
+
Snapshots: 0 total
|
|
21
|
+
Time: 1.234 s
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
console.log('1. Parsing Jest output...');
|
|
25
|
+
const testResults = parseJestOutput(jestOutput);
|
|
26
|
+
|
|
27
|
+
if (!testResults) {
|
|
28
|
+
console.error('❌ Failed to parse Jest output');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log('✅ Parsed successfully:');
|
|
33
|
+
console.log(` - Total: ${testResults.total}`);
|
|
34
|
+
console.log(` - Passed: ${testResults.passed}`);
|
|
35
|
+
console.log(` - Failed: ${testResults.failed}`);
|
|
36
|
+
console.log(` - Result: ${testResults.result}\n`);
|
|
37
|
+
|
|
38
|
+
// Test with MCP .taf file
|
|
39
|
+
const tafPath = path.join('/Users/wolfejam/FAF/claude-faf-mcp', '.taf');
|
|
40
|
+
|
|
41
|
+
console.log('2. Checking .taf file...');
|
|
42
|
+
if (!fs.existsSync(tafPath)) {
|
|
43
|
+
console.error('❌ No .taf file found at:', tafPath);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log('✅ Found .taf file\n');
|
|
47
|
+
|
|
48
|
+
console.log('3. Reading current .taf file...');
|
|
49
|
+
const beforeContent = fs.readFileSync(tafPath, 'utf-8');
|
|
50
|
+
const beforeLines = beforeContent.split('\n').filter(line => line.includes('timestamp')).length;
|
|
51
|
+
console.log(`✅ Current test runs: ${beforeLines}\n`);
|
|
52
|
+
|
|
53
|
+
console.log('4. Updating .taf file...');
|
|
54
|
+
const updated = await updateTafFile(tafPath, testResults);
|
|
55
|
+
|
|
56
|
+
if (!updated) {
|
|
57
|
+
console.error('❌ Failed to update .taf file');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
console.log('✅ .taf file updated\n');
|
|
61
|
+
|
|
62
|
+
console.log('5. Verifying update...');
|
|
63
|
+
const afterContent = fs.readFileSync(tafPath, 'utf-8');
|
|
64
|
+
const afterLines = afterContent.split('\n').filter(line => line.includes('timestamp')).length;
|
|
65
|
+
console.log(`✅ Test runs after update: ${afterLines}`);
|
|
66
|
+
|
|
67
|
+
if (afterLines !== beforeLines + 1) {
|
|
68
|
+
console.error('❌ Expected one additional run');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('\n✅ MCP test PASSED');
|
|
73
|
+
console.log('\nThe action successfully:');
|
|
74
|
+
console.log(' - Parsed Jest output (15/15 tests)');
|
|
75
|
+
console.log(' - Updated .taf file');
|
|
76
|
+
console.log(' - Added new test run entry');
|
|
77
|
+
console.log(' - Maintained proper format');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
testMCP().catch(error => {
|
|
81
|
+
console.error('❌ Test failed:', error);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone test script to simulate the action
|
|
3
|
+
* Tests the action logic without GitHub Actions environment
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { parseJestOutput } from './src/parsers/jest';
|
|
9
|
+
import { updateTafFile } from './src/taf-core';
|
|
10
|
+
|
|
11
|
+
async function testAction() {
|
|
12
|
+
console.log('Starting standalone action test...\n');
|
|
13
|
+
|
|
14
|
+
// Simulate Jest output from faf-cli
|
|
15
|
+
const jestOutput = `
|
|
16
|
+
PASS src/commands/taf-init.test.ts
|
|
17
|
+
PASS src/commands/taf-log.test.ts
|
|
18
|
+
PASS src/commands/taf-validate.test.ts
|
|
19
|
+
PASS src/commands/taf-stats.test.ts
|
|
20
|
+
|
|
21
|
+
Test Suites: 18 passed, 18 total
|
|
22
|
+
Tests: 173 passed, 173 total
|
|
23
|
+
Snapshots: 0 total
|
|
24
|
+
Time: 2.512 s
|
|
25
|
+
Ran all test suites.
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
console.log('1. Parsing Jest output...');
|
|
29
|
+
const testResults = parseJestOutput(jestOutput);
|
|
30
|
+
|
|
31
|
+
if (!testResults) {
|
|
32
|
+
console.error('❌ Failed to parse Jest output');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('✅ Parsed successfully:');
|
|
37
|
+
console.log(` - Total: ${testResults.total}`);
|
|
38
|
+
console.log(` - Passed: ${testResults.passed}`);
|
|
39
|
+
console.log(` - Failed: ${testResults.failed}`);
|
|
40
|
+
console.log(` - Result: ${testResults.result}\n`);
|
|
41
|
+
|
|
42
|
+
// Test with faf-cli .taf file
|
|
43
|
+
const tafPath = path.join('/Users/wolfejam/FAF/cli', '.taf');
|
|
44
|
+
|
|
45
|
+
console.log('2. Checking .taf file...');
|
|
46
|
+
if (!fs.existsSync(tafPath)) {
|
|
47
|
+
console.error('❌ No .taf file found at:', tafPath);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
console.log('✅ Found .taf file\n');
|
|
51
|
+
|
|
52
|
+
console.log('3. Reading current .taf file...');
|
|
53
|
+
const beforeContent = fs.readFileSync(tafPath, 'utf-8');
|
|
54
|
+
const beforeLines = beforeContent.split('\n').filter(line => line.includes('timestamp')).length;
|
|
55
|
+
console.log(`✅ Current test runs: ${beforeLines}\n`);
|
|
56
|
+
|
|
57
|
+
console.log('4. Updating .taf file...');
|
|
58
|
+
const updated = await updateTafFile(tafPath, testResults);
|
|
59
|
+
|
|
60
|
+
if (!updated) {
|
|
61
|
+
console.error('❌ Failed to update .taf file');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
console.log('✅ .taf file updated\n');
|
|
65
|
+
|
|
66
|
+
console.log('5. Verifying update...');
|
|
67
|
+
const afterContent = fs.readFileSync(tafPath, 'utf-8');
|
|
68
|
+
const afterLines = afterContent.split('\n').filter(line => line.includes('timestamp')).length;
|
|
69
|
+
console.log(`✅ Test runs after update: ${afterLines}`);
|
|
70
|
+
|
|
71
|
+
if (afterLines !== beforeLines + 1) {
|
|
72
|
+
console.error('❌ Expected one additional run');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('\n✅ Standalone test PASSED');
|
|
77
|
+
console.log('\nThe action successfully:');
|
|
78
|
+
console.log(' - Parsed Jest output (173/173 tests)');
|
|
79
|
+
console.log(' - Updated .taf file');
|
|
80
|
+
console.log(' - Added new test run entry');
|
|
81
|
+
console.log(' - Maintained proper format');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
testAction().catch(error => {
|
|
85
|
+
console.error('❌ Test failed:', error);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Jest output parser
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { parseJestOutput } from '../src/parsers/jest';
|
|
6
|
+
|
|
7
|
+
describe('Jest Parser', () => {
|
|
8
|
+
describe('parseJestOutput', () => {
|
|
9
|
+
it('should parse all passing tests', () => {
|
|
10
|
+
const output = `
|
|
11
|
+
Test Suites: 18 passed, 18 total
|
|
12
|
+
Tests: 173 passed, 173 total
|
|
13
|
+
Snapshots: 0 total
|
|
14
|
+
Time: 2.5s
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const result = parseJestOutput(output);
|
|
18
|
+
|
|
19
|
+
expect(result).not.toBeNull();
|
|
20
|
+
expect(result?.total).toBe(173);
|
|
21
|
+
expect(result?.passed).toBe(173);
|
|
22
|
+
expect(result?.failed).toBe(0);
|
|
23
|
+
expect(result?.result).toBe('PASSED');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should parse with failed tests', () => {
|
|
27
|
+
const output = `
|
|
28
|
+
Test Suites: 1 failed, 17 passed, 18 total
|
|
29
|
+
Tests: 1 failed, 172 passed, 173 total
|
|
30
|
+
Snapshots: 0 total
|
|
31
|
+
Time: 2.5s
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const result = parseJestOutput(output);
|
|
35
|
+
|
|
36
|
+
expect(result).not.toBeNull();
|
|
37
|
+
expect(result?.total).toBe(173);
|
|
38
|
+
expect(result?.passed).toBe(172);
|
|
39
|
+
expect(result?.failed).toBe(1);
|
|
40
|
+
expect(result?.result).toBe('FAILED');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should parse with skipped tests', () => {
|
|
44
|
+
const output = `
|
|
45
|
+
Test Suites: 18 passed, 18 total
|
|
46
|
+
Tests: 2 skipped, 171 passed, 173 total
|
|
47
|
+
Snapshots: 0 total
|
|
48
|
+
Time: 2.5s
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
const result = parseJestOutput(output);
|
|
52
|
+
|
|
53
|
+
expect(result).not.toBeNull();
|
|
54
|
+
expect(result?.total).toBe(173);
|
|
55
|
+
expect(result?.passed).toBe(171);
|
|
56
|
+
expect(result?.failed).toBe(0);
|
|
57
|
+
expect(result?.skipped).toBe(2);
|
|
58
|
+
expect(result?.result).toBe('PASSED');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should parse with mixed results', () => {
|
|
62
|
+
const output = `
|
|
63
|
+
Test Suites: 2 failed, 16 passed, 18 total
|
|
64
|
+
Tests: 5 failed, 3 skipped, 165 passed, 173 total
|
|
65
|
+
Snapshots: 0 total
|
|
66
|
+
Time: 2.5s
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const result = parseJestOutput(output);
|
|
70
|
+
|
|
71
|
+
expect(result).not.toBeNull();
|
|
72
|
+
expect(result?.total).toBe(173);
|
|
73
|
+
expect(result?.passed).toBe(165);
|
|
74
|
+
expect(result?.failed).toBe(5);
|
|
75
|
+
expect(result?.skipped).toBe(3);
|
|
76
|
+
expect(result?.result).toBe('FAILED');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle output without test line', () => {
|
|
80
|
+
const output = `
|
|
81
|
+
Some random output
|
|
82
|
+
No test results here
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const result = parseJestOutput(output);
|
|
86
|
+
|
|
87
|
+
expect(result).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should handle malformed output', () => {
|
|
91
|
+
const output = `
|
|
92
|
+
Tests: invalid data
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
const result = parseJestOutput(output);
|
|
96
|
+
|
|
97
|
+
expect(result).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle real CLI output with colors', () => {
|
|
101
|
+
const output = `
|
|
102
|
+
PASS src/commands/taf-init.test.ts
|
|
103
|
+
PASS src/commands/taf-log.test.ts
|
|
104
|
+
PASS src/commands/taf-validate.test.ts
|
|
105
|
+
|
|
106
|
+
Test Suites: 18 passed, 18 total
|
|
107
|
+
Tests: 173 passed, 173 total
|
|
108
|
+
Snapshots: 0 total
|
|
109
|
+
Time: 2.512 s
|
|
110
|
+
Ran all test suites.
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
const result = parseJestOutput(output);
|
|
114
|
+
|
|
115
|
+
expect(result).not.toBeNull();
|
|
116
|
+
expect(result?.total).toBe(173);
|
|
117
|
+
expect(result?.passed).toBe(173);
|
|
118
|
+
expect(result?.failed).toBe(0);
|
|
119
|
+
expect(result?.result).toBe('PASSED');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|