Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
38 changes: 38 additions & 0 deletions bounty-notes/issue-83-external-bounty-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# External bounty flow verification for issue #83

Fixes #83.

Issue #83 covers an externally-created bounty where the repository app was not installed at issue creation time. This contribution adds a deterministic fixture and validator for that path so the pending-approval and approved bounty states can be checked without depending on a live CodeBounty environment.

## What is covered

- Repository: `CodeBountyOrg/BountyTestRepository`
- Issue: `#83`
- Source: external sponsor
- No-app-installed path: `installation.app_installed === false`
- Pending state: `pending_maintainer_approval`
- Available state: `available`
- Required PR syntax: `fixes #83`
- Visible bounty amount: `$150 USD`

## Validation commands

```bash
node --check scripts/validate-codebounty-external-bounty.mjs
node scripts/validate-codebounty-external-bounty.mjs test-fixtures/codebounty-external-issue-83.json
python3 - <<'PY'
import json
from pathlib import Path
fixture = json.loads(Path('test-fixtures/codebounty-external-issue-83.json').read_text())
note = Path('bounty-notes/issue-83-external-bounty-flow.md').read_text()
assert fixture['issue']['number'] == 83
assert fixture['installation']['app_installed'] is False
assert fixture['bounty']['amount_usd'] == 150
assert 'fixes #83' in note.lower()
PY
git diff --check
```

## Payout note

The GitHub-visible bounty is treated as submitted-visible only. CodeBounty platform application and maintainer acceptance remain payout blockers until verified.
48 changes: 48 additions & 0 deletions scripts/validate-codebounty-external-bounty.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env node
import { readFileSync } from 'node:fs';

const assert = (condition, message) => {
if (!condition) {
throw new Error(message);
}
};

const loadPayload = (path) => {
try {
return JSON.parse(readFileSync(path, 'utf8'));
} catch (error) {
throw new Error(`Unable to read external bounty fixture at ${path}: ${error.message}`);
}
};

const payloadPath = process.argv[2] ?? 'test-fixtures/codebounty-external-issue-83.json';
const payload = loadPayload(payloadPath);

assert(payload.repository?.full_name === 'CodeBountyOrg/BountyTestRepository', 'repository.full_name must match the target repository');
assert(payload.issue?.number === 83, 'issue.number must be 83');
assert(payload.issue?.source === 'external_sponsor', 'issue.source must identify an external sponsor');
assert(payload.installation?.app_installed === false, 'fixture must cover the no-app-installed path');
assert(payload.bounty?.amount_usd === 150, 'bounty.amount_usd must stay at the announced $150');
assert(payload.bounty?.currency === 'USD', 'bounty currency must be USD');
assert(Array.isArray(payload.timeline) && payload.timeline.length >= 2, 'timeline must include pending and available states');

const states = payload.timeline.map((event) => event.status);
assert(states[0] === 'pending_maintainer_approval', 'first timeline state must be pending maintainer approval');
assert(states.includes('available'), 'timeline must include the available state after approval');

const pending = payload.timeline.find((event) => event.status === 'pending_maintainer_approval');
const available = payload.timeline.find((event) => event.status === 'available');
assert(pending?.requires_maintainer_action === true, 'pending state must require maintainer action');
assert(available?.requires_maintainer_action === false, 'available state must no longer require maintainer action');
assert(available?.required_pr_syntax === 'fixes #83', 'available state must preserve required PR syntax');

const normalized = {
issue: payload.issue.number,
amount_usd: payload.bounty.amount_usd,
external_sponsor: payload.issue.source === 'external_sponsor',
no_app_installed_path: payload.installation.app_installed === false,
final_status: available.status,
required_pr_syntax: available.required_pr_syntax,
};

console.log(JSON.stringify(normalized, null, 2));
33 changes: 33 additions & 0 deletions test-fixtures/codebounty-external-issue-83.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"event": "external_bounty.created",
"repository": {
"full_name": "CodeBountyOrg/BountyTestRepository"
},
"issue": {
"number": 83,
"title": "Creating issue as an external user without App Installed",
"source": "external_sponsor"
},
"installation": {
"app_installed": false,
"expected_path": "maintainer_approval_required"
},
"bounty": {
"amount_usd": 150,
"currency": "USD",
"provider": "CodeBounty"
},
"timeline": [
{
"status": "pending_maintainer_approval",
"requires_maintainer_action": true,
"comment_type": "bounty_pending_approval"
},
{
"status": "available",
"requires_maintainer_action": false,
"comment_type": "bounty_announcement",
"required_pr_syntax": "fixes #83"
}
]
}