test: require TEST_PR_ID for write integration tests to prevent accidental mutations

Write tests no longer pick a random open PR. RUN_WRITE_TESTS=true alone is not
sufficient — TEST_PR_ID must also be set, otherwise each write test is skipped
with a warning. Added try/finally cleanup blocks so approval/comment/task state
is always restored even on assertion failures. Updated .env.example and README
to document the new requirement.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 23:25:40 +02:00
parent ab73c92e6d
commit 9c7d983df4
3 changed files with 50 additions and 34 deletions

View File

@@ -294,6 +294,7 @@ describe('Error Handling', () => {
});
const RUN_WRITES = process.env.RUN_WRITE_TESTS === 'true';
const TEST_PR_ID = process.env.TEST_PR_ID ? parseInt(process.env.TEST_PR_ID, 10) : undefined;
describe('Repository / Workspace (read)', () => {
let client: BitbucketClient;
@@ -340,31 +341,28 @@ describe('Repository / Workspace (read)', () => {
});
});
describe('PR Write Operations (requires RUN_WRITE_TESTS=true)', () => {
describe('PR Write Operations (requires RUN_WRITE_TESTS=true and TEST_PR_ID)', () => {
let client: BitbucketClient;
beforeAll(() => {
client = new BitbucketClient();
});
it('should approve a PR', async () => {
it('should approve then immediately unapprove a PR', async () => {
if (!HAS_TOKEN || !RUN_WRITES) return;
const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG, { state: 'OPEN' });
if (prs.length === 0) { console.log('No open PRs, skipping'); return; }
const result = await client.approvePullRequest(WORKSPACE, REPO_SLUG, prs[0].id);
expect(result).toBeDefined();
});
if (!TEST_PR_ID) { console.log('⚠️ TEST_PR_ID not set, skipping approve/unapprove test'); return; }
it('should unapprove a PR', async () => {
if (!HAS_TOKEN || !RUN_WRITES) return;
const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG, { state: 'OPEN' });
if (prs.length === 0) { console.log('No open PRs, skipping'); return; }
const result = await client.unapprovePullRequest(WORKSPACE, REPO_SLUG, prs[0].id);
expect(result).toHaveProperty('success', true);
await client.approvePullRequest(WORKSPACE, REPO_SLUG, TEST_PR_ID);
try {
const unapproved = await client.unapprovePullRequest(WORKSPACE, REPO_SLUG, TEST_PR_ID);
expect(unapproved).toHaveProperty('success', true);
} finally {
await client.unapprovePullRequest(WORKSPACE, REPO_SLUG, TEST_PR_ID).catch(() => {});
}
});
});
describe('Comment / Task Write Operations (requires RUN_WRITE_TESTS=true)', () => {
describe('Comment / Task Write Operations (requires RUN_WRITE_TESTS=true and TEST_PR_ID)', () => {
let client: BitbucketClient;
beforeAll(() => {
@@ -373,35 +371,33 @@ describe('Comment / Task Write Operations (requires RUN_WRITE_TESTS=true)', () =
it('should add, update, then delete a comment', async () => {
if (!HAS_TOKEN || !RUN_WRITES) return;
const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG, { state: 'OPEN' });
if (prs.length === 0) { console.log('No open PRs, skipping'); return; }
const prId = prs[0].id;
if (!TEST_PR_ID) { console.log('⚠️ TEST_PR_ID not set, skipping comment test'); return; }
const added = await client.addPullRequestComment(WORKSPACE, REPO_SLUG, prId, { content: 'Integration test comment' });
const added = await client.addPullRequestComment(WORKSPACE, REPO_SLUG, TEST_PR_ID, { content: 'Integration test comment' });
expect(added).toHaveProperty('id');
const commentId = added.id;
const updated = await client.updatePullRequestComment(WORKSPACE, REPO_SLUG, prId, commentId, { content: 'Updated comment' });
expect(updated).toHaveProperty('id', commentId);
const deleted = await client.deletePullRequestComment(WORKSPACE, REPO_SLUG, prId, commentId);
expect(deleted).toHaveProperty('success', true);
try {
const updated = await client.updatePullRequestComment(WORKSPACE, REPO_SLUG, TEST_PR_ID, commentId, { content: 'Updated comment' });
expect(updated).toHaveProperty('id', commentId);
} finally {
await client.deletePullRequestComment(WORKSPACE, REPO_SLUG, TEST_PR_ID, commentId).catch(() => {});
}
});
it('should create, resolve, then delete a task', async () => {
if (!HAS_TOKEN || !RUN_WRITES) return;
const prs = await client.listPullRequests(WORKSPACE, REPO_SLUG, { state: 'OPEN' });
if (prs.length === 0) { console.log('No open PRs, skipping'); return; }
const prId = prs[0].id;
if (!TEST_PR_ID) { console.log('⚠️ TEST_PR_ID not set, skipping task test'); return; }
const created = await client.createPullRequestTask(WORKSPACE, REPO_SLUG, prId, { content: 'Integration test task' });
const created = await client.createPullRequestTask(WORKSPACE, REPO_SLUG, TEST_PR_ID, { content: 'Integration test task' });
expect(created).toHaveProperty('id');
const taskId = created.id;
const resolved = await client.updatePullRequestTask(WORKSPACE, REPO_SLUG, prId, taskId, { state: 'RESOLVED' });
expect(resolved).toBeDefined();
const deleted = await client.deletePullRequestTask(WORKSPACE, REPO_SLUG, prId, taskId);
expect(deleted).toHaveProperty('success', true);
try {
const resolved = await client.updatePullRequestTask(WORKSPACE, REPO_SLUG, TEST_PR_ID, taskId, { state: 'RESOLVED' });
expect(resolved).toBeDefined();
} finally {
await client.deletePullRequestTask(WORKSPACE, REPO_SLUG, TEST_PR_ID, taskId).catch(() => {});
}
});
});