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
34 changes: 34 additions & 0 deletions bounty-notes/issue-42-webhooks-test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# CodeBounty webhook verification for issue #42

Fixes #42.

This adds a deterministic webhook payload validation fixture for the CodeBounty webhook test issue. The fixture and validator let maintainers confirm that a bounty webhook event carries the fields required to route a bounty notification back to the correct GitHub issue and visible reward.

## Added artifacts

- `test-fixtures/codebounty-webhook-issue-42.json` — sample `bounty.available` payload for `CodeBountyOrg/BountyTestRepository#42` with the $150 visible bounty amount.
- `scripts/validate-codebounty-webhook.mjs` — dependency-free Node.js validator that parses a webhook JSON file/stdin and checks:
- event name
- repository full name
- issue number
- positive bounty amount
- bounty status

## Validation commands

```bash
node --check scripts/validate-codebounty-webhook.mjs
node scripts/validate-codebounty-webhook.mjs test-fixtures/codebounty-webhook-issue-42.json
```

Expected normalized output:

```json
{
"event": "bounty.available",
"repository": "CodeBountyOrg/BountyTestRepository",
"issue": "#42",
"bounty": "$150",
"status": "available"
}
```
59 changes: 59 additions & 0 deletions scripts/validate-codebounty-webhook.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node
import { readFileSync } from 'node:fs';

const requiredString = (payload, path) => {
const value = path.split('.').reduce((current, key) => current?.[key], payload);
if (typeof value !== 'string' || value.trim() === '') {
throw new Error(`Missing required string: ${path}`);
}
return value.trim();
};

const requiredNumber = (payload, path) => {
const value = path.split('.').reduce((current, key) => current?.[key], payload);
if (typeof value !== 'number' || Number.isNaN(value)) {
throw new Error(`Missing required number: ${path}`);
}
return value;
};

const readPayload = () => {
const inputPath = process.argv[2];
const raw = inputPath ? readFileSync(inputPath, 'utf8') : readFileSync(0, 'utf8');
try {
return JSON.parse(raw);
} catch (error) {
throw new Error(`Webhook payload is not valid JSON: ${error.message}`);
}
};

const normalizeWebhookPayload = (payload) => {
const event = requiredString(payload, 'event');
const repository = requiredString(payload, 'repository.full_name');
const issueNumber = requiredNumber(payload, 'issue.number');
const bountyAmount = requiredNumber(payload, 'bounty.amount_usd');
const bountyStatus = requiredString(payload, 'bounty.status');

if (issueNumber <= 0) {
throw new Error('issue.number must be positive');
}

if (bountyAmount <= 0) {
throw new Error('bounty.amount_usd must be positive');
}

return {
event,
repository,
issue: `#${issueNumber}`,
bounty: `$${bountyAmount}`,
status: bountyStatus,
};
};

const main = () => {
const normalized = normalizeWebhookPayload(readPayload());
console.log(JSON.stringify(normalized, null, 2));
};

main();
15 changes: 15 additions & 0 deletions test-fixtures/codebounty-webhook-issue-42.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"event": "bounty.available",
"repository": {
"full_name": "CodeBountyOrg/BountyTestRepository"
},
"issue": {
"number": 42,
"title": "webhooks test"
},
"bounty": {
"amount_usd": 150,
"status": "available",
"provider": "CodeBounty"
}
}