Skip to content

Commit fd1a1f3

Browse files
avrabeclaude
andcommitted
fix: dependabot labels, enqueueTask wiring, auto AI review on PR open
- Add ensureLabelsExist() — additive-only label creation (no delete/update) - getTargetIssueLabels() now includes dependabot_generation.default_labels - Add extractLabelsFromConfig() helper to dependabot.js - Wire enqueueTask into both configureRepository call sites - generate-dependabot handler ensures labels before applying config - PR opened: check for missing dependabot.yml and enqueue generation - PR opened: auto-trigger AI review when ai_review.enabled - Fix pull_request.opened early returns blocking non-bot PR logic - Disable required_pull_request_reviews (set to null) in config.yml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3fe3ada commit fd1a1f3

10 files changed

Lines changed: 289 additions & 37 deletions

File tree

__tests__/integration/app.test.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ jest.mock('../../src/reporting.js', () => ({
1919
jest.mock('../../src/dependabot.js', () => ({
2020
checkDependabotConfiguration: jest.fn(),
2121
checkExistingDependabotConfig: jest.fn(),
22+
extractLabelsFromConfig: jest.fn().mockReturnValue([]),
2223
fixDependabotPRLabels: jest.fn(),
2324
generateDependabotConfig: jest.fn(),
2425
applyDependabotConfig: jest.fn()
2526
}));
2627

28+
jest.mock('../../src/labels.js', () => ({
29+
ensureLabelsExist: jest.fn().mockResolvedValue(undefined),
30+
synchronizeIssueLabels: jest.fn().mockResolvedValue(undefined)
31+
}));
32+
2733
jest.mock('../../src/merge-strategy.js', () => ({
2834
handleSignedCommitMerge: jest.fn(),
2935
checkPRMergeStrategy: jest.fn()
@@ -75,10 +81,12 @@ import { generateConfigurationReport } from '../../src/reporting.js';
7581
import {
7682
checkDependabotConfiguration,
7783
checkExistingDependabotConfig,
84+
extractLabelsFromConfig,
7885
fixDependabotPRLabels,
7986
generateDependabotConfig,
8087
applyDependabotConfig
8188
} from '../../src/dependabot.js';
89+
import { ensureLabelsExist } from '../../src/labels.js';
8290
import { handleSignedCommitMerge, checkPRMergeStrategy } from '../../src/merge-strategy.js';
8391
import { reviewPullRequest } from '../../src/ai-review.js';
8492
import { isProcessed, markProcessed } from '../../src/idempotency.js';
@@ -534,7 +542,9 @@ describe('app', () => {
534542
const context = createRepoCreatedContext();
535543
await handlers['repository.created'](context);
536544

537-
expect(configureRepository).toHaveBeenCalledWith(context.octokit, context.payload.repository);
545+
expect(configureRepository).toHaveBeenCalledWith(
546+
context.octokit, context.payload.repository, undefined, { enqueueTask: null }
547+
);
538548
expect(context.octokit.issues.create).toHaveBeenCalledWith(
539549
expect.objectContaining({
540550
owner: 'pulseengine',
@@ -753,7 +763,7 @@ describe('app', () => {
753763
await handlers['issue_comment.created'](context);
754764

755765
expect(configureRepository).toHaveBeenCalledWith(
756-
context.octokit, context.payload.repository
766+
context.octokit, context.payload.repository, undefined, { enqueueTask: null }
757767
);
758768
expect(context.octokit.issues.createComment).toHaveBeenCalledWith(
759769
expect.objectContaining({

__tests__/integration/dependabot.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jest.mock('../../src/logger.js', () => {
1919
import {
2020
applyDependabotConfig,
2121
checkExistingDependabotConfig,
22+
extractLabelsFromConfig,
2223
fixDependabotPRLabels,
2324
checkDependabotConfiguration,
2425
generateDependabotConfig,
@@ -632,6 +633,43 @@ describe('dependabot', () => {
632633
});
633634
});
634635

636+
// ---------------------------------------------------------------------------
637+
// extractLabelsFromConfig
638+
// ---------------------------------------------------------------------------
639+
describe('extractLabelsFromConfig', () => {
640+
it('returns unique labels from all update entries', () => {
641+
const result = extractLabelsFromConfig({
642+
version: 2,
643+
updates: [
644+
{ 'package-ecosystem': 'npm', labels: ['dependencies', 'javascript'] },
645+
{ 'package-ecosystem': 'docker', labels: ['dependencies', 'docker'] }
646+
]
647+
});
648+
expect(result).toEqual(expect.arrayContaining(['dependencies', 'javascript', 'docker']));
649+
expect(result).toHaveLength(3);
650+
});
651+
652+
it('returns empty array for missing config', () => {
653+
expect(extractLabelsFromConfig(null)).toEqual([]);
654+
expect(extractLabelsFromConfig(undefined)).toEqual([]);
655+
});
656+
657+
it('returns empty array for config without updates', () => {
658+
expect(extractLabelsFromConfig({ version: 2 })).toEqual([]);
659+
expect(extractLabelsFromConfig({})).toEqual([]);
660+
});
661+
662+
it('skips updates without labels', () => {
663+
const result = extractLabelsFromConfig({
664+
updates: [
665+
{ 'package-ecosystem': 'npm', directory: '/' },
666+
{ 'package-ecosystem': 'docker', labels: ['docker'] }
667+
]
668+
});
669+
expect(result).toEqual(['docker']);
670+
});
671+
});
672+
635673
// ---------------------------------------------------------------------------
636674
// generateDependabotConfig
637675
// ---------------------------------------------------------------------------

__tests__/integration/labels.test.js

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { synchronizeIssueLabels } from '../../src/labels.js';
1+
import { synchronizeIssueLabels, ensureLabelsExist } from '../../src/labels.js';
22

33
function createMockOctokit() {
44
return {
@@ -114,3 +114,76 @@ describe('synchronizeIssueLabels', () => {
114114
).rejects.toThrow('API down');
115115
});
116116
});
117+
118+
describe('ensureLabelsExist', () => {
119+
let octokit;
120+
beforeEach(() => { octokit = createMockOctokit(); });
121+
122+
it('creates missing labels with DEPENDABOT_LABEL_DEFAULTS', async () => {
123+
octokit.paginate.mockResolvedValue([]);
124+
125+
await ensureLabelsExist(octokit, 'owner', 'repo', ['dependencies']);
126+
127+
expect(octokit.request).toHaveBeenCalledWith(
128+
'POST /repos/{owner}/{repo}/labels',
129+
expect.objectContaining({ name: 'dependencies', color: '0366d6', description: 'Dependency updates' })
130+
);
131+
});
132+
133+
it('uses fallback defaults for unknown labels', async () => {
134+
octokit.paginate.mockResolvedValue([]);
135+
136+
await ensureLabelsExist(octokit, 'owner', 'repo', ['custom-label']);
137+
138+
expect(octokit.request).toHaveBeenCalledWith(
139+
'POST /repos/{owner}/{repo}/labels',
140+
expect.objectContaining({ name: 'custom-label', color: 'ededed', description: 'Automated label' })
141+
);
142+
});
143+
144+
it('skips labels that already exist', async () => {
145+
octokit.paginate.mockResolvedValue([{ name: 'dependencies', color: '0366d6', description: 'Deps' }]);
146+
147+
await ensureLabelsExist(octokit, 'owner', 'repo', ['dependencies']);
148+
149+
expect(octokit.request).not.toHaveBeenCalled();
150+
});
151+
152+
it('never updates or deletes existing labels', async () => {
153+
octokit.paginate.mockResolvedValue([
154+
{ name: 'dependencies', color: 'ffffff', description: 'Old' },
155+
{ name: 'extra', color: 'aaa', description: 'Extra' }
156+
]);
157+
158+
await ensureLabelsExist(octokit, 'owner', 'repo', ['dependencies']);
159+
160+
expect(octokit.request).not.toHaveBeenCalledWith(
161+
'PATCH /repos/{owner}/{repo}/labels/{name}',
162+
expect.anything()
163+
);
164+
expect(octokit.request).not.toHaveBeenCalledWith(
165+
'DELETE /repos/{owner}/{repo}/labels/{name}',
166+
expect.anything()
167+
);
168+
});
169+
170+
it('creates only missing labels from a mixed set', async () => {
171+
octokit.paginate.mockResolvedValue([{ name: 'dependencies', color: '0366d6', description: 'Deps' }]);
172+
173+
await ensureLabelsExist(octokit, 'owner', 'repo', ['dependencies', 'automation']);
174+
175+
expect(octokit.request).toHaveBeenCalledTimes(1);
176+
expect(octokit.request).toHaveBeenCalledWith(
177+
'POST /repos/{owner}/{repo}/labels',
178+
expect.objectContaining({ name: 'automation', color: '0e8a16' })
179+
);
180+
});
181+
182+
it('handles empty label list', async () => {
183+
octokit.paginate.mockResolvedValue([{ name: 'existing' }]);
184+
185+
await ensureLabelsExist(octokit, 'owner', 'repo', []);
186+
187+
expect(octokit.request).not.toHaveBeenCalled();
188+
});
189+
});

__tests__/smoke/imports.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,14 @@ describe('src module imports', () => {
6363
it('src/labels.js exports expected functions', async () => {
6464
const mod = await import('../../src/labels.js');
6565
expect(typeof mod.synchronizeIssueLabels).toBe('function');
66+
expect(typeof mod.ensureLabelsExist).toBe('function');
6667
});
6768

6869
it('src/dependabot.js exports expected functions', async () => {
6970
const mod = await import('../../src/dependabot.js');
7071
expect(typeof mod.applyDependabotConfig).toBe('function');
7172
expect(typeof mod.checkExistingDependabotConfig).toBe('function');
73+
expect(typeof mod.extractLabelsFromConfig).toBe('function');
7274
expect(typeof mod.fixDependabotPRLabels).toBe('function');
7375
expect(typeof mod.checkDependabotConfiguration).toBe('function');
7476
});

__tests__/unit/config.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,5 +194,37 @@ describe('config', () => {
194194
_setConfigForTesting({});
195195
expect(getTargetIssueLabels()).toEqual([]);
196196
});
197+
198+
it('includes labels from dependabot_generation.default_labels', () => {
199+
_setConfigForTesting({
200+
dependabot_generation: { default_labels: ['dependencies', 'github-actions'] }
201+
});
202+
const result = getTargetIssueLabels();
203+
expect(result).toHaveLength(2);
204+
expect(result[0].name).toBe('dependencies');
205+
expect(result[0].color).toBe('0366d6');
206+
expect(result[1].name).toBe('github-actions');
207+
expect(result[1].color).toBe('ededed');
208+
});
209+
210+
it('deduplicates labels from dependabot and dependabot_generation', () => {
211+
_setConfigForTesting({
212+
dependabot: { updates: [{ labels: ['dependencies'] }] },
213+
dependabot_generation: { default_labels: ['dependencies', 'automation'] }
214+
});
215+
const result = getTargetIssueLabels();
216+
expect(result).toHaveLength(2);
217+
expect(result.map((l) => l.name)).toEqual(['dependencies', 'automation']);
218+
});
219+
220+
it('deduplicates against issue_labels too', () => {
221+
_setConfigForTesting({
222+
issue_labels: [{ name: 'dependencies', color: 'custom', description: 'Custom' }],
223+
dependabot_generation: { default_labels: ['dependencies'] }
224+
});
225+
const result = getTargetIssueLabels();
226+
expect(result).toHaveLength(1);
227+
expect(result[0].color).toBe('custom');
228+
});
197229
});
198230
});

config.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ branch_protection:
3232
strict: true
3333
contexts: []
3434
enforce_admins: true
35-
required_pull_request_reviews:
36-
required_approving_review_count: 0
37-
dismiss_stale_reviews: true
38-
require_code_owner_reviews: false
35+
required_pull_request_reviews: null
3936
required_linear_history: false
4037
required_conversation_resolution: false
4138
restrictions: null

0 commit comments

Comments
 (0)