Skip to content

test: Comprehensive Unit Tests for Contributor Role Rewards - Bounty #49 ($75)#92

Open
zhaog100 wants to merge 3 commits into
ubiquity-os:mainfrom
zhaog100:test/contributor-role-rewards-unit-tests
Open

test: Comprehensive Unit Tests for Contributor Role Rewards - Bounty #49 ($75)#92
zhaog100 wants to merge 3 commits into
ubiquity-os:mainfrom
zhaog100:test/contributor-role-rewards-unit-tests

Conversation

@zhaog100
Copy link
Copy Markdown

Comprehensive Unit Tests for Contributor Role Rewards

Closes #49 ($75)

Overview

This PR provides comprehensive unit tests covering all three Contributor Role Rewards plugin variants:

Test Coverage (131 tests)

Contributor Class Detection

  • Basic classification (ISSUER, ASSIGNEE, COLLABORATOR, CONTRIBUTOR)
  • Priority ordering (ISSUER > ASSIGNEE > COLLABORATOR > CONTRIBUTOR)
  • Edge cases: undefined fields, empty arrays, case sensitivity, self-assignment

No Config v1 — Reward Engine

  • Base amount from Priority labels (default 200)
  • Time multiplier calculation
  • Per-contributor event counting and sorting
  • Event filtering (counted vs non-counted)
  • Case-insensitive label matching
  • Null/undefined actor handling
  • Custom pricing support

With Config v3 — Target Matching

  • All 7 TargetRole variants (ISSUER, ASSIGNEE, COLLABORATOR, CONTRIBUTOR, REVIEWERS, COMMENTERS, COMMITTERS)
  • Cross-role matching (role lists vs class-based)
  • Pull/Issue context separation
  • Multiple target matching

Negative Value Events

  • Negative reward values
  • Negative label overrides (partial and full reduction)
  • Aggregation with mixed positive/negative rewards
  • Zero-value filtering

Label Overrides

  • Positive, negative, zero, and stacked overrides
  • Unrecognized labels pass through

Integration Scenarios

  • Complete lifecycle (issue → label → PR → review → close)
  • Self-assignment edge case
  • No assignee scenario
  • Org member collaborator detection
  • Multi-event aggregation

How to Run

npm install
npx jest --config jest.config.cjs

Files Changed

Test Results

Test Suites: 2 passed, 2 total
Tests:       131 passed, 131 total

Bounty

This addresses #49 ($75)

…biquity-os#49 ($75)

Covers three plugin variants:
- ubiquity-os#46: No Config v1 (reward-engine: computeRewards, verifyContributorRole)
- ubiquity-os#47: With Config v3 (rewards handler: pull/issue context separation)
- ubiquity-os#48: Contributor Class detection (ISSUER/ASSIGNEE/COLLABORATOR/CONTRIBUTOR)

Test coverage (131 tests total):
- Contributor class detection with priority ordering and edge cases
- Target role matching (all 7 TargetRole variants)
- Reward calculation with pull/issue context separation
- Negative value events and label overrides
- Event name parsing and config lookup
- Full lifecycle integration scenarios
- No Config v1 reward engine (pricing, multipliers, event filtering)
- Edge cases: no actor, undefined fields, case sensitivity, self-assignment

Bounty: ubiquity-os#49
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

Adds two reward plugins and full test infra. Introduces contributor-rewards-with-config (plugin entrypoints, worker/action, reward computation, type schemas, package/ts/jest configs, CSpell/Knip, strings) and proposals/webhook-rewards (manifest, package, webhook/reward-engine/timeline handlers, tsconfig). Adds extensive Jest tests, MSW mocks, in-memory test DB, and fixtures. Adds top-level tsconfig, jest.config.cjs, and package.json for tests. Updates .gitignore to ignore node_modules.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding comprehensive unit tests for Contributor Role Rewards with bounty reference.
Description check ✅ Passed The description is well-organized, detailing test coverage across three plugin variants, test scenarios, and infrastructure changes directly related to the changeset.
Linked Issues check ✅ Passed The PR fulfills #49's objective: comprehensive unit tests covering role detection, contributor classification, reward computation, label overrides, and integration scenarios across all three plugin variants.
Out of Scope Changes check ✅ Passed All changes are in-scope: test files, test infrastructure (jest.config.cjs, tsconfig.json, package.json), and supporting source files from referenced PRs (#86, #88, #90) included for test context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (4)
contributor-rewards-with-config/src/types/context.ts (1)

2-2: Tighten SupportedEvents beyond plain string.
Line 2 currently allows any event name, so unsupported events won’t be caught at compile time. Prefer a union of allowed events (or an imported canonical event type).

contributor-rewards-with-config/src/types/env.ts (1)

1-1: Env type is too permissive for compile-time safety.

Line [1] allows arbitrary keys, so misspelled env names won’t be caught. Consider an explicit interface for known keys used by this plugin.

contributor-rewards-with-config/src/types/process-env.d.ts (1)

3-5: Make GITHUB_TOKEN optional in typing.

Currently typed as required, but the environment variable is unused in the codebase. While this specific case has no runtime impact, making unused or optionally-required env vars optional in types prevents accidental assumptions about their availability. If GITHUB_TOKEN becomes used later, the optional type will correctly enforce validation at the call site rather than masking missing-token failures.

Suggested fix
 declare global {
   namespace NodeJS {
     interface ProcessEnv {
-      GITHUB_TOKEN: string;
+      GITHUB_TOKEN?: string;
     }
   }
 }
contributor-rewards-with-config/src/handlers/rewards.ts (1)

35-51: Consider adding exhaustiveness check.

The switch lacks a default case. While currently exhaustive, adding new TargetRole values could cause silent fallthrough. TypeScript's exhaustiveness checking can be explicitly enforced:

♻️ Optional: Add exhaustiveness check
     case TargetRole.COMMITTERS:
       return committers?.includes(userLogin) ?? false;
+    default: {
+      const _exhaustive: never = targetRole;
+      return false;
+    }
   }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e0486eff-906c-4bba-9cc1-f64f377c5369

📥 Commits

Reviewing files that changed from the base of the PR and between edb3383 and 1a351a6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (35)
  • .gitignore
  • contributor-rewards-with-config/.cspell.json
  • contributor-rewards-with-config/.github/knip.ts
  • contributor-rewards-with-config/jest.config.ts
  • contributor-rewards-with-config/package.json
  • contributor-rewards-with-config/src/action.ts
  • contributor-rewards-with-config/src/handlers/rewards.ts
  • contributor-rewards-with-config/src/index.ts
  • contributor-rewards-with-config/src/types.ts
  • contributor-rewards-with-config/src/types/context.ts
  • contributor-rewards-with-config/src/types/env.ts
  • contributor-rewards-with-config/src/types/index.ts
  • contributor-rewards-with-config/src/types/plugin-input.ts
  • contributor-rewards-with-config/src/types/process-env.d.ts
  • contributor-rewards-with-config/src/worker.ts
  • contributor-rewards-with-config/strings.json
  • contributor-rewards-with-config/tests/__mocks__/db.ts
  • contributor-rewards-with-config/tests/__mocks__/handlers.ts
  • contributor-rewards-with-config/tests/__mocks__/helpers.ts
  • contributor-rewards-with-config/tests/__mocks__/issue-template.ts
  • contributor-rewards-with-config/tests/__mocks__/node.ts
  • contributor-rewards-with-config/tests/__mocks__/strings.ts
  • contributor-rewards-with-config/tests/__mocks__/users-get.json
  • contributor-rewards-with-config/tests/main.test.ts
  • contributor-rewards-with-config/tsconfig.json
  • jest.config.cjs
  • package.json
  • proposals/webhook-rewards/manifest.json
  • proposals/webhook-rewards/package.json
  • proposals/webhook-rewards/src/index.ts
  • proposals/webhook-rewards/src/reward-engine.ts
  • proposals/webhook-rewards/src/webhook-handler.ts
  • proposals/webhook-rewards/tsconfig.json
  • tests/contributor-role-rewards.test.ts
  • tsconfig.json

Comment thread contributor-rewards-with-config/src/index.ts Outdated
Comment thread contributor-rewards-with-config/src/index.ts
Comment thread contributor-rewards-with-config/src/index.ts Outdated
import { ExecutionContext } from "hono";
import manifest from "../manifest.json" with { type: "json" };
import { runPlugin } from "./index";
import { Env, envSchema, PluginSettings, pluginSettingsSchema, SupportedEvents } from "./types";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing schema exports will break plugin creation.

Line 7 imports envSchema and pluginSettingsSchema, but the provided type modules only expose Env/PluginSettings types. Unless ./types re-exports real schemas from another file, this entrypoint will not build.

Also applies to: 30-32

Comment thread contributor-rewards-with-config/tests/__mocks__/handlers.ts Outdated
Comment thread proposals/webhook-rewards/src/index.ts Outdated
Comment thread proposals/webhook-rewards/src/reward-engine.ts
Comment thread proposals/webhook-rewards/src/webhook-handler.ts Outdated
Comment thread proposals/webhook-rewards/src/webhook-handler.ts Outdated
Comment thread tsconfig.json Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2ebbb8d0-a550-4303-9376-417ad560cef2

📥 Commits

Reviewing files that changed from the base of the PR and between 1a351a6 and 1e4a74d.

📒 Files selected for processing (14)
  • contributor-rewards-with-config/src/index.ts
  • contributor-rewards-with-config/src/types/env.ts
  • contributor-rewards-with-config/src/types/plugin-input.ts
  • contributor-rewards-with-config/src/types/process-env.d.ts
  • contributor-rewards-with-config/tests/__mocks__/handlers.ts
  • contributor-rewards-with-config/tests/__mocks__/helpers.ts
  • contributor-rewards-with-config/tests/__mocks__/issue-template.ts
  • contributor-rewards-with-config/tests/__mocks__/users-get.json
  • jest.config.cjs
  • proposals/webhook-rewards/src/index.ts
  • proposals/webhook-rewards/src/reward-engine.ts
  • proposals/webhook-rewards/src/webhook-handler.ts
  • tests/contributor-role-rewards.test.ts
  • tsconfig.json
✅ Files skipped from review due to trivial changes (6)
  • contributor-rewards-with-config/tests/mocks/users-get.json
  • contributor-rewards-with-config/tests/mocks/issue-template.ts
  • tsconfig.json
  • contributor-rewards-with-config/tests/mocks/helpers.ts
  • contributor-rewards-with-config/src/types/plugin-input.ts
  • tests/contributor-role-rewards.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • contributor-rewards-with-config/src/types/process-env.d.ts
  • contributor-rewards-with-config/src/types/env.ts
  • jest.config.cjs
  • contributor-rewards-with-config/tests/mocks/handlers.ts
  • contributor-rewards-with-config/src/index.ts
  • proposals/webhook-rewards/src/webhook-handler.ts
  • proposals/webhook-rewards/src/index.ts

Comment on lines +41 to +61
// Step 1: determine base amount from Priority labels
let baseAmount = 200; // sensible default
for (const label of labels) {
const pricingKey = Object.keys(pricing).find(
(k) => label.toLowerCase() === k.toLowerCase()
);
if (pricingKey && isPriorityLabel(pricingKey)) {
baseAmount = pricing[pricingKey];
}
}

// Step 2: determine multiplier from Time labels
let multiplier = 1;
for (const label of labels) {
const pricingKey = Object.keys(pricing).find(
(k) => label.toLowerCase() === k.toLowerCase()
);
if (pricingKey && isTimeLabel(pricingKey)) {
multiplier = pricing[pricingKey];
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make label-to-pricing resolution deterministic and single-pass.

Lines 41-61 currently pick the last matching Priority/Time label, so payout changes with label order. This can produce inconsistent rewards for the same label set.

💡 Proposed fix
-  // Step 1: determine base amount from Priority labels
-  let baseAmount = 200; // sensible default
-  for (const label of labels) {
-    const pricingKey = Object.keys(pricing).find(
-      (k) => label.toLowerCase() === k.toLowerCase()
-    );
-    if (pricingKey && isPriorityLabel(pricingKey)) {
-      baseAmount = pricing[pricingKey];
-    }
-  }
-
-  // Step 2: determine multiplier from Time labels
-  let multiplier = 1;
-  for (const label of labels) {
-    const pricingKey = Object.keys(pricing).find(
-      (k) => label.toLowerCase() === k.toLowerCase()
-    );
-    if (pricingKey && isTimeLabel(pricingKey)) {
-      multiplier = pricing[pricingKey];
-    }
-  }
+  // Step 1 & 2: derive base amount + multiplier deterministically
+  const pricingByLabel = new Map(
+    Object.entries(pricing).map(([key, value]) => [key.toLowerCase(), { key, value }])
+  );
+  const matchedPriorities: number[] = [];
+  const matchedTimes: number[] = [];
+
+  for (const label of labels) {
+    const match = pricingByLabel.get(label.toLowerCase());
+    if (!match) continue;
+    if (isPriorityLabel(match.key)) matchedPriorities.push(match.value);
+    if (isTimeLabel(match.key)) matchedTimes.push(match.value);
+  }
+
+  const baseAmount = matchedPriorities.length ? Math.max(...matchedPriorities) : 200;
+  const multiplier = matchedTimes.length ? Math.max(...matchedTimes) : 1;

Comment on lines +65 to +67
for (const evt of timelineEvents) {
if (!countedEvents.includes(evt.event)) continue;
const login = evt.actor?.login;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize counted-event comparisons to avoid case-sensitive misses.

Line 66 uses case-sensitive matching. A mixed-case event string would be silently skipped.

💡 Proposed fix
-  for (const evt of timelineEvents) {
-    if (!countedEvents.includes(evt.event)) continue;
+  const countedEventSet = new Set(countedEvents.map((e) => e.toLowerCase()));
+  for (const evt of timelineEvents) {
+    if (!countedEventSet.has(evt.event.toLowerCase())) continue;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const evt of timelineEvents) {
if (!countedEvents.includes(evt.event)) continue;
const login = evt.actor?.login;
const countedEventSet = new Set(countedEvents.map((e) => e.toLowerCase()));
for (const evt of timelineEvents) {
if (!countedEventSet.has(evt.event.toLowerCase())) continue;
const login = evt.actor?.login;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generalized "GitHub Webhook + Contributor Role -> Rewards" Unit Tests

1 participant