Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Port tests
  • Loading branch information
mbg committed Mar 1, 2026
commit 8eb0202e9d4ba01c2d56e0ce47f216da4a368502
250 changes: 250 additions & 0 deletions pr-checks/sync_back.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
#!/usr/bin/env npx tsx

/*
Tests for the sync_back.ts script
*/

import * as assert from "node:assert/strict";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { afterEach, beforeEach, describe, it } from "node:test";

import {
scanGeneratedWorkflows,
updateSyncTs,
updateTemplateFiles,
} from "./sync_back";

let testDir: string;
let workflowDir: string;
let checksDir: string;
let syncTsPath: string;

beforeEach(() => {
/** Set up temporary directories and files for testing */
testDir = fs.mkdtempSync(path.join(os.tmpdir(), "sync-back-test-"));
workflowDir = path.join(testDir, ".github", "workflows");
checksDir = path.join(testDir, "pr-checks", "checks");
fs.mkdirSync(workflowDir, { recursive: true });
fs.mkdirSync(checksDir, { recursive: true });

// Create sync.ts file path
syncTsPath = path.join(testDir, "pr-checks", "sync.ts");
});

afterEach(() => {
/** Clean up temporary directories */
fs.rmSync(testDir, { recursive: true, force: true });
});

describe("scanGeneratedWorkflows", () => {
it("basic workflow scanning", () => {
/** Test basic workflow scanning functionality */
const workflowContent = `
name: Test Workflow
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v5
- uses: actions/setup-go@v6
`;

fs.writeFileSync(path.join(workflowDir, "__test.yml"), workflowContent);

const result = scanGeneratedWorkflows(workflowDir);

assert.equal(result["actions/checkout"], "v4");
assert.equal(result["actions/setup-node"], "v5");
assert.equal(result["actions/setup-go"], "v6");
});

it("scanning workflows with version comments", () => {
/** Test scanning workflows with version comments */
const workflowContent = `
name: Test Workflow
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0
- uses: actions/setup-python@v6 # Latest Python
`;

fs.writeFileSync(path.join(workflowDir, "__test.yml"), workflowContent);

const result = scanGeneratedWorkflows(workflowDir);

assert.equal(result["actions/checkout"], "v4");
assert.equal(
result["ruby/setup-ruby"],
"44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0",
);
assert.equal(result["actions/setup-python"], "v6 # Latest Python");
});

it("ignores local actions", () => {
/** Test that local actions (starting with ./) are ignored */
const workflowContent = `
name: Test Workflow
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/local-action
- uses: ./another-local-action@v1
`;

fs.writeFileSync(path.join(workflowDir, "__test.yml"), workflowContent);

const result = scanGeneratedWorkflows(workflowDir);

assert.equal(result["actions/checkout"], "v4");
assert.equal("./.github/actions/local-action" in result, false);
assert.equal("./another-local-action" in result, false);
});
});

describe("updateSyncTs", () => {
it("updates sync.ts file", () => {
/** Test updating sync.ts file */
const syncTsContent = `
const steps = [
{
uses: "actions/setup-node@v4",
with: { "node-version": "16" },
},
{
uses: "actions/setup-go@v5",
with: { "go-version": "1.19" },
},
];
`;

fs.writeFileSync(syncTsPath, syncTsContent);

const actionVersions = {
"actions/setup-node": "v5",
"actions/setup-go": "v6",
};

const result = updateSyncTs(syncTsPath, actionVersions);
assert.equal(result, true);

const updatedContent = fs.readFileSync(syncTsPath, "utf8");

assert.ok(updatedContent.includes('uses: "actions/setup-node@v5"'));
assert.ok(updatedContent.includes('uses: "actions/setup-go@v6"'));
});

it("strips comments from versions", () => {
/** Test updating sync.ts file when versions have comments */
const syncTsContent = `
const steps = [
{
uses: "actions/setup-node@v4",
with: { "node-version": "16" },
},
];
`;

fs.writeFileSync(syncTsPath, syncTsContent);

const actionVersions = {
"actions/setup-node": "v5 # Latest version",
};

const result = updateSyncTs(syncTsPath, actionVersions);
assert.equal(result, true);

const updatedContent = fs.readFileSync(syncTsPath, "utf8");

// sync.ts should get the version without comment
assert.ok(updatedContent.includes('uses: "actions/setup-node@v5"'));
assert.ok(!updatedContent.includes("# Latest version"));
});

it("returns false when no changes are needed", () => {
/** Test that updateSyncTs returns false when no changes are needed */
const syncTsContent = `
const steps = [
{
uses: "actions/setup-node@v5",
with: { "node-version": "16" },
},
];
`;

fs.writeFileSync(syncTsPath, syncTsContent);

const actionVersions = {
"actions/setup-node": "v5",
};

const result = updateSyncTs(syncTsPath, actionVersions);
assert.equal(result, false);
});
});

describe("updateTemplateFiles", () => {
it("updates template files", () => {
/** Test updating template files */
const templateContent = `
name: Test Template
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 16
`;

const templatePath = path.join(checksDir, "test.yml");
fs.writeFileSync(templatePath, templateContent);

const actionVersions = {
"actions/checkout": "v4",
"actions/setup-node": "v5 # Latest",
};

const result = updateTemplateFiles(checksDir, actionVersions);
assert.equal(result.length, 1);
assert.ok(result.includes(templatePath));

const updatedContent = fs.readFileSync(templatePath, "utf8");

assert.ok(updatedContent.includes("uses: actions/checkout@v4"));
assert.ok(updatedContent.includes("uses: actions/setup-node@v5 # Latest"));
});

it("preserves version comments", () => {
/** Test that updating template files preserves version comments */
const templateContent = `
name: Test Template
steps:
- uses: ruby/setup-ruby@44511735964dcb71245e7e55f72539531f7bc0eb # v1.256.0
`;

const templatePath = path.join(checksDir, "test.yml");
fs.writeFileSync(templatePath, templateContent);

const actionVersions = {
"ruby/setup-ruby":
"55511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0",
};

const result = updateTemplateFiles(checksDir, actionVersions);
assert.equal(result.length, 1);

const updatedContent = fs.readFileSync(templatePath, "utf8");

assert.ok(
updatedContent.includes(
"uses: ruby/setup-ruby@55511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0",
),
);
});
});
11 changes: 7 additions & 4 deletions pr-checks/sync_back.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const SYNC_TS_PATH = path.join(THIS_DIR, "sync.ts");
* @param workflowDir - Path to .github/workflows directory
* @returns Map from action names to their latest versions (including comments)
*/
function scanGeneratedWorkflows(workflowDir: string): Record<string, string> {
export function scanGeneratedWorkflows(workflowDir: string): Record<string, string> {
const actionVersions: Record<string, string> = {};

const generatedFiles = fs
Expand Down Expand Up @@ -71,7 +71,7 @@ function scanGeneratedWorkflows(workflowDir: string): Record<string, string> {
* @param actionVersions - Map of action names to versions (may include comments)
* @returns True if the file was modified, false otherwise
*/
function updateSyncTs(
export function updateSyncTs(
syncTsPath: string,
actionVersions: Record<string, string>,
): boolean {
Expand Down Expand Up @@ -120,7 +120,7 @@ function updateSyncTs(
* @param actionVersions - Map of action names to versions (may include comments)
* @returns List of files that were modified
*/
function updateTemplateFiles(
export function updateTemplateFiles(
checksDir: string,
actionVersions: Record<string, string>,
): string[] {
Expand Down Expand Up @@ -214,4 +214,7 @@ function main(): number {
return 0;
}

process.exit(main());
// Only call `main` if this script was run directly.
if (require.main === module) {
process.exit(main());
}