@dependabit/action 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/action.yml +85 -0
- package/dist/actions/check.d.ts +33 -0
- package/dist/actions/check.d.ts.map +1 -0
- package/dist/actions/check.js +162 -0
- package/dist/actions/check.js.map +1 -0
- package/dist/actions/generate.d.ts +9 -0
- package/dist/actions/generate.d.ts.map +1 -0
- package/dist/actions/generate.js +152 -0
- package/dist/actions/generate.js.map +1 -0
- package/dist/actions/update.d.ts +9 -0
- package/dist/actions/update.d.ts.map +1 -0
- package/dist/actions/update.js +246 -0
- package/dist/actions/update.js.map +1 -0
- package/dist/actions/validate.d.ts +33 -0
- package/dist/actions/validate.d.ts.map +1 -0
- package/dist/actions/validate.js +226 -0
- package/dist/actions/validate.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +114 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +154 -0
- package/dist/logger.js.map +1 -0
- package/dist/utils/agent-config.d.ts +31 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +42 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/utils/agent-router.d.ts +33 -0
- package/dist/utils/agent-router.d.ts.map +1 -0
- package/dist/utils/agent-router.js +57 -0
- package/dist/utils/agent-router.js.map +1 -0
- package/dist/utils/errors.d.ts +51 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +219 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/inputs.d.ts +35 -0
- package/dist/utils/inputs.d.ts.map +1 -0
- package/dist/utils/inputs.js +47 -0
- package/dist/utils/inputs.js.map +1 -0
- package/dist/utils/metrics.d.ts +66 -0
- package/dist/utils/metrics.d.ts.map +1 -0
- package/dist/utils/metrics.js +116 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/outputs.d.ts +43 -0
- package/dist/utils/outputs.d.ts.map +1 -0
- package/dist/utils/outputs.js +146 -0
- package/dist/utils/outputs.js.map +1 -0
- package/dist/utils/performance.d.ts +100 -0
- package/dist/utils/performance.d.ts.map +1 -0
- package/dist/utils/performance.js +185 -0
- package/dist/utils/performance.js.map +1 -0
- package/dist/utils/reporter.d.ts +43 -0
- package/dist/utils/reporter.d.ts.map +1 -0
- package/dist/utils/reporter.js +122 -0
- package/dist/utils/reporter.js.map +1 -0
- package/dist/utils/secrets.d.ts +45 -0
- package/dist/utils/secrets.d.ts.map +1 -0
- package/dist/utils/secrets.js +94 -0
- package/dist/utils/secrets.js.map +1 -0
- package/package.json +45 -0
- package/src/actions/check.ts +223 -0
- package/src/actions/generate.ts +181 -0
- package/src/actions/update.ts +284 -0
- package/src/actions/validate.ts +292 -0
- package/src/index.ts +43 -0
- package/src/logger.test.ts +200 -0
- package/src/logger.ts +210 -0
- package/src/utils/agent-config.ts +61 -0
- package/src/utils/agent-router.ts +67 -0
- package/src/utils/errors.ts +251 -0
- package/src/utils/inputs.ts +75 -0
- package/src/utils/metrics.ts +169 -0
- package/src/utils/outputs.ts +202 -0
- package/src/utils/performance.ts +248 -0
- package/src/utils/reporter.ts +169 -0
- package/src/utils/secrets.ts +124 -0
- package/test/actions/check.test.ts +216 -0
- package/test/actions/generate.test.ts +82 -0
- package/test/actions/update.test.ts +70 -0
- package/test/actions/validate.test.ts +257 -0
- package/test/utils/agent-config.test.ts +112 -0
- package/test/utils/agent-router.test.ts +129 -0
- package/test/utils/metrics.test.ts +221 -0
- package/test/utils/reporter.test.ts +196 -0
- package/test/utils/secrets.test.ts +217 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summary Reporter
|
|
3
|
+
* Generates human-readable summaries for dependency change reports
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface DependencyChange {
|
|
7
|
+
dependency: {
|
|
8
|
+
id: string;
|
|
9
|
+
name?: string | undefined;
|
|
10
|
+
url: string;
|
|
11
|
+
type?: string | undefined;
|
|
12
|
+
};
|
|
13
|
+
severity: 'breaking' | 'major' | 'minor';
|
|
14
|
+
changes: string[];
|
|
15
|
+
oldVersion?: string | undefined;
|
|
16
|
+
newVersion?: string | undefined;
|
|
17
|
+
releaseNotes?: string;
|
|
18
|
+
diff?: string;
|
|
19
|
+
contentDiff?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class SummaryReporter {
|
|
23
|
+
/**
|
|
24
|
+
* Generates a summary report for all dependency changes
|
|
25
|
+
*/
|
|
26
|
+
generateSummary(changes: DependencyChange[]): string {
|
|
27
|
+
if (changes.length === 0) {
|
|
28
|
+
return '✅ All dependencies are up to date. No changes detected.';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const breaking = changes.filter((c) => c.severity === 'breaking');
|
|
32
|
+
const major = changes.filter((c) => c.severity === 'major');
|
|
33
|
+
const minor = changes.filter((c) => c.severity === 'minor');
|
|
34
|
+
|
|
35
|
+
let summary = '# Dependency Changes Detected\n\n';
|
|
36
|
+
summary += `**Total Changes**: ${changes.length}\n`;
|
|
37
|
+
summary += `- 🔴 Breaking: ${breaking.length}\n`;
|
|
38
|
+
summary += `- 🟡 Major: ${major.length}\n`;
|
|
39
|
+
summary += `- 🟢 Minor: ${minor.length}\n\n`;
|
|
40
|
+
|
|
41
|
+
if (breaking.length > 0) {
|
|
42
|
+
summary += '## ⚠️ Breaking Changes\n\n';
|
|
43
|
+
breaking.forEach((change) => {
|
|
44
|
+
summary += this.formatChangeItem(change);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (major.length > 0) {
|
|
49
|
+
summary += '## 🔔 Major Updates\n\n';
|
|
50
|
+
major.forEach((change) => {
|
|
51
|
+
summary += this.formatChangeItem(change);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (minor.length > 0) {
|
|
56
|
+
summary += '## 📝 Minor Updates\n\n';
|
|
57
|
+
minor.forEach((change) => {
|
|
58
|
+
summary += this.formatChangeItem(change);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return summary;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generates detailed issue body for a single dependency change
|
|
67
|
+
*/
|
|
68
|
+
generateIssueBody(change: DependencyChange): string {
|
|
69
|
+
const { dependency, severity, changes, oldVersion, newVersion, releaseNotes } = change;
|
|
70
|
+
|
|
71
|
+
let body = '';
|
|
72
|
+
|
|
73
|
+
// Header with severity indicator
|
|
74
|
+
const severityEmoji = {
|
|
75
|
+
breaking: '⚠️',
|
|
76
|
+
major: '🔔',
|
|
77
|
+
minor: '📝'
|
|
78
|
+
}[severity];
|
|
79
|
+
|
|
80
|
+
body += `${severityEmoji} **${severity.toUpperCase()}** dependency update detected\n\n`;
|
|
81
|
+
|
|
82
|
+
// Dependency information
|
|
83
|
+
body += `## Dependency: ${dependency.name || dependency.id}\n\n`;
|
|
84
|
+
body += `- **URL**: ${dependency.url}\n`;
|
|
85
|
+
if (dependency.type) {
|
|
86
|
+
body += `- **Type**: ${dependency.type}\n`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Version change
|
|
90
|
+
if (oldVersion && newVersion) {
|
|
91
|
+
body += `\n## Version Change\n\n`;
|
|
92
|
+
body += `\`${oldVersion}\` → \`${newVersion}\`\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Changes detected
|
|
96
|
+
body += `\n## Changes Detected\n\n`;
|
|
97
|
+
changes.forEach((change) => {
|
|
98
|
+
body += `- ${change}\n`;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Release notes if available
|
|
102
|
+
if (releaseNotes) {
|
|
103
|
+
body += `\n## Release Notes\n\n`;
|
|
104
|
+
body += releaseNotes;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Action required for breaking changes
|
|
108
|
+
if (severity === 'breaking') {
|
|
109
|
+
body += `\n## ⚠️ Action Required\n\n`;
|
|
110
|
+
body += `This is a **breaking change** that may require updates to your code.\n`;
|
|
111
|
+
body += `Please review the changes and update your implementation accordingly.\n`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return body;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Formats change summary for display
|
|
119
|
+
*/
|
|
120
|
+
formatChangeSummary(change: {
|
|
121
|
+
changes: string[];
|
|
122
|
+
oldVersion?: string;
|
|
123
|
+
newVersion?: string;
|
|
124
|
+
contentDiff?: string;
|
|
125
|
+
}): string {
|
|
126
|
+
let summary = '';
|
|
127
|
+
|
|
128
|
+
if (change.oldVersion && change.newVersion) {
|
|
129
|
+
summary += `Version: ${change.oldVersion} → ${change.newVersion}\n`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (change.changes.includes('content')) {
|
|
133
|
+
summary += 'content updated\n';
|
|
134
|
+
if (change.contentDiff) {
|
|
135
|
+
summary += `Changes: ${change.contentDiff}\n`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (change.changes.includes('metadata')) {
|
|
140
|
+
summary += 'metadata updated\n';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (change.changes.includes('version')) {
|
|
144
|
+
summary += 'version updated\n';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return summary.trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Formats a single change item for the summary
|
|
152
|
+
*/
|
|
153
|
+
private formatChangeItem(change: DependencyChange): string {
|
|
154
|
+
const name = change.dependency.name || change.dependency.id;
|
|
155
|
+
let item = `### ${name}\n\n`;
|
|
156
|
+
item += `- **URL**: ${change.dependency.url}\n`;
|
|
157
|
+
|
|
158
|
+
if (change.oldVersion && change.newVersion) {
|
|
159
|
+
item += `- **Version**: \`${change.oldVersion}\` → \`${change.newVersion}\`\n`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (change.changes.length > 0) {
|
|
163
|
+
item += `- **Changes**: ${change.changes.join(', ')}\n`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
item += '\n';
|
|
167
|
+
return item;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret resolution utility for GitHub Actions and environment variables
|
|
3
|
+
* Safely resolves secrets from GitHub Secrets and environment variables
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface SecretResolverConfig {
|
|
7
|
+
prefix?: string;
|
|
8
|
+
enableCache?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DependencyAuthConfig {
|
|
12
|
+
[domain: string]: {
|
|
13
|
+
secretName: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolves secrets from environment variables and GitHub Secrets
|
|
19
|
+
*/
|
|
20
|
+
export class SecretResolver {
|
|
21
|
+
private prefix: string;
|
|
22
|
+
private enableCache: boolean;
|
|
23
|
+
private cache: Map<string, string>;
|
|
24
|
+
|
|
25
|
+
constructor(config: SecretResolverConfig = {}) {
|
|
26
|
+
this.prefix = config.prefix || '';
|
|
27
|
+
this.enableCache = config.enableCache ?? false;
|
|
28
|
+
this.cache = new Map();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a single secret by name
|
|
33
|
+
*/
|
|
34
|
+
async resolve(secretName: string): Promise<string> {
|
|
35
|
+
// Parse GitHub Actions secret reference format: ${{ secrets.NAME }}
|
|
36
|
+
const match = secretName.match(/\$\{\{\s*secrets\.([A-Z_]+)\s*\}\}/);
|
|
37
|
+
const actualName = match ? match[1] : secretName;
|
|
38
|
+
const envKey = this.prefix + actualName;
|
|
39
|
+
|
|
40
|
+
// Check cache first
|
|
41
|
+
if (this.enableCache && this.cache.has(envKey)) {
|
|
42
|
+
return this.cache.get(envKey)!;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Resolve from environment
|
|
46
|
+
const value = process.env[envKey];
|
|
47
|
+
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
throw new Error(`Secret ${actualName} not found`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Cache if enabled
|
|
53
|
+
if (this.enableCache) {
|
|
54
|
+
this.cache.set(envKey, value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve multiple secrets at once
|
|
62
|
+
*/
|
|
63
|
+
async resolveMultiple(
|
|
64
|
+
secretNames: string[],
|
|
65
|
+
options?: { allowPartial?: boolean }
|
|
66
|
+
): Promise<Record<string, string>> {
|
|
67
|
+
const result: Record<string, string> = {};
|
|
68
|
+
const errors: string[] = [];
|
|
69
|
+
|
|
70
|
+
for (const name of secretNames) {
|
|
71
|
+
try {
|
|
72
|
+
result[name] = await this.resolve(name);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (options?.allowPartial) {
|
|
75
|
+
// Skip missing secrets when allowPartial is true
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
errors.push((error as Error).message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (errors.length > 0 && !options?.allowPartial) {
|
|
83
|
+
throw new Error(errors[0]); // Throw first error
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolve per-dependency authentication configuration
|
|
91
|
+
*/
|
|
92
|
+
async resolveDependencyAuth(config: DependencyAuthConfig): Promise<Record<string, string>> {
|
|
93
|
+
const result: Record<string, string> = {};
|
|
94
|
+
|
|
95
|
+
for (const [domain, authConfig] of Object.entries(config)) {
|
|
96
|
+
const secretValue = await this.resolve(authConfig.secretName);
|
|
97
|
+
result[domain] = secretValue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate secret name format
|
|
105
|
+
*/
|
|
106
|
+
validate(secretName: string): boolean {
|
|
107
|
+
// Handle GitHub Actions secret reference
|
|
108
|
+
if (secretName.startsWith('${{')) {
|
|
109
|
+
const match = secretName.match(/\$\{\{\s*secrets\.([A-Z_][A-Z0-9_]*)\s*\}\}/);
|
|
110
|
+
return match !== null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Standard environment variable naming convention
|
|
114
|
+
// Must start with letter or underscore, contain only alphanumeric and underscores
|
|
115
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(secretName);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Clear the secret cache
|
|
120
|
+
*/
|
|
121
|
+
clearCache(): void {
|
|
122
|
+
this.cache.clear();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { checkAction } from '../../src/actions/check.js';
|
|
3
|
+
|
|
4
|
+
// Mock the Monitor module
|
|
5
|
+
vi.mock('@dependabit/monitor', () => {
|
|
6
|
+
class MockMonitor {
|
|
7
|
+
checkAll = vi.fn().mockImplementation((dependencies) => {
|
|
8
|
+
// Return error for invalid URLs
|
|
9
|
+
return Promise.resolve(
|
|
10
|
+
dependencies.map((dep) => {
|
|
11
|
+
if (dep.url === 'https://invalid-url.com') {
|
|
12
|
+
return {
|
|
13
|
+
dependency: dep,
|
|
14
|
+
hasChanged: false,
|
|
15
|
+
error: 'Failed to fetch URL content: connect ENOTFOUND invalid-url.com'
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
dependency: dep,
|
|
20
|
+
hasChanged: false,
|
|
21
|
+
newSnapshot: {
|
|
22
|
+
stateHash: dep.currentStateHash,
|
|
23
|
+
fetchedAt: new Date()
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
Monitor: MockMonitor
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Mock the IssueManager module
|
|
37
|
+
vi.mock('@dependabit/github-client', () => {
|
|
38
|
+
class MockIssueManager {
|
|
39
|
+
createIssue = vi.fn().mockResolvedValue({
|
|
40
|
+
number: 123,
|
|
41
|
+
url: 'https://github.com/owner/repo/issues/123',
|
|
42
|
+
labels: ['dependabit', 'severity:major']
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
findExistingIssue = vi.fn().mockResolvedValue(null);
|
|
46
|
+
|
|
47
|
+
updateIssue = vi.fn().mockResolvedValue({
|
|
48
|
+
number: 123,
|
|
49
|
+
url: 'https://github.com/owner/repo/issues/123',
|
|
50
|
+
labels: ['dependabit', 'severity:major']
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class MockRateLimitHandler {
|
|
55
|
+
checkRateLimit = vi.fn().mockResolvedValue({
|
|
56
|
+
limit: 5000,
|
|
57
|
+
remaining: 4000,
|
|
58
|
+
reset: new Date(Date.now() + 3600000),
|
|
59
|
+
used: 1000
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
waitIfNeeded = vi.fn().mockResolvedValue(undefined);
|
|
63
|
+
|
|
64
|
+
reserveBudget = vi.fn().mockResolvedValue({
|
|
65
|
+
reserved: true
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
IssueManager: MockIssueManager,
|
|
71
|
+
RateLimitHandler: MockRateLimitHandler
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('Check Action', () => {
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
vi.clearAllMocks();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('checkAction', () => {
|
|
81
|
+
it('should check all dependencies in manifest', async () => {
|
|
82
|
+
const manifest = {
|
|
83
|
+
version: '1.0.0',
|
|
84
|
+
dependencies: [
|
|
85
|
+
{
|
|
86
|
+
id: 'dep1',
|
|
87
|
+
url: 'https://github.com/owner/repo',
|
|
88
|
+
type: 'reference-implementation',
|
|
89
|
+
accessMethod: 'github-api',
|
|
90
|
+
currentStateHash: 'hash1',
|
|
91
|
+
monitoring: { enabled: true }
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const result = await checkAction(manifest);
|
|
97
|
+
|
|
98
|
+
expect(result).toBeDefined();
|
|
99
|
+
expect(result.checked).toBeGreaterThan(0);
|
|
100
|
+
expect(result.changes).toBeDefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should skip disabled dependencies', async () => {
|
|
104
|
+
const manifest = {
|
|
105
|
+
version: '1.0.0',
|
|
106
|
+
dependencies: [
|
|
107
|
+
{
|
|
108
|
+
id: 'enabled',
|
|
109
|
+
url: 'https://github.com/owner/repo1',
|
|
110
|
+
type: 'reference-implementation',
|
|
111
|
+
accessMethod: 'github-api',
|
|
112
|
+
currentStateHash: 'hash1',
|
|
113
|
+
monitoring: { enabled: true }
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'disabled',
|
|
117
|
+
url: 'https://github.com/owner/repo2',
|
|
118
|
+
type: 'reference-implementation',
|
|
119
|
+
accessMethod: 'github-api',
|
|
120
|
+
currentStateHash: 'hash2',
|
|
121
|
+
monitoring: { enabled: false }
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const result = await checkAction(manifest);
|
|
127
|
+
|
|
128
|
+
expect(result.checked).toBe(1);
|
|
129
|
+
expect(result.skipped).toBe(1);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should create issues for detected changes', async () => {
|
|
133
|
+
const manifest = {
|
|
134
|
+
version: '1.0.0',
|
|
135
|
+
dependencies: [
|
|
136
|
+
{
|
|
137
|
+
id: 'changed-dep',
|
|
138
|
+
url: 'https://github.com/owner/repo',
|
|
139
|
+
type: 'reference-implementation',
|
|
140
|
+
accessMethod: 'github-api',
|
|
141
|
+
currentStateHash: 'old-hash',
|
|
142
|
+
monitoring: { enabled: true }
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const result = await checkAction(manifest);
|
|
148
|
+
|
|
149
|
+
if (result.changes.length > 0) {
|
|
150
|
+
expect(result.issuesCreated).toBeGreaterThan(0);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle errors gracefully', async () => {
|
|
155
|
+
const manifest = {
|
|
156
|
+
version: '1.0.0',
|
|
157
|
+
dependencies: [
|
|
158
|
+
{
|
|
159
|
+
id: 'invalid',
|
|
160
|
+
url: 'https://invalid-url.com',
|
|
161
|
+
type: 'documentation',
|
|
162
|
+
accessMethod: 'http',
|
|
163
|
+
currentStateHash: 'hash1',
|
|
164
|
+
monitoring: { enabled: true }
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = await checkAction(manifest);
|
|
170
|
+
|
|
171
|
+
expect(result).toBeDefined();
|
|
172
|
+
expect(result.errors).toBeGreaterThan(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should respect rate limits', async () => {
|
|
176
|
+
const manifest = {
|
|
177
|
+
version: '1.0.0',
|
|
178
|
+
dependencies: Array.from({ length: 100 }, (_, i) => ({
|
|
179
|
+
id: `dep${i}`,
|
|
180
|
+
url: `https://github.com/owner/repo${i}`,
|
|
181
|
+
type: 'reference-implementation',
|
|
182
|
+
accessMethod: 'github-api',
|
|
183
|
+
currentStateHash: `hash${i}`,
|
|
184
|
+
monitoring: { enabled: true }
|
|
185
|
+
}))
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const result = await checkAction(manifest);
|
|
189
|
+
|
|
190
|
+
expect(result).toBeDefined();
|
|
191
|
+
expect(result.rateLimitWarnings).toBeDefined();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should update manifest with new state hashes', async () => {
|
|
195
|
+
const manifest = {
|
|
196
|
+
version: '1.0.0',
|
|
197
|
+
dependencies: [
|
|
198
|
+
{
|
|
199
|
+
id: 'dep1',
|
|
200
|
+
url: 'https://github.com/owner/repo',
|
|
201
|
+
type: 'reference-implementation',
|
|
202
|
+
accessMethod: 'github-api',
|
|
203
|
+
currentStateHash: 'old-hash',
|
|
204
|
+
lastChecked: '2024-01-01T00:00:00Z',
|
|
205
|
+
monitoring: { enabled: true }
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const result = await checkAction(manifest);
|
|
211
|
+
|
|
212
|
+
expect(result.updatedManifest).toBeDefined();
|
|
213
|
+
expect(result.updatedManifest.dependencies[0].lastChecked).not.toBe('2024-01-01T00:00:00Z');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('GenerateAction', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.clearAllMocks();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe('run', () => {
|
|
9
|
+
it('should parse action inputs', async () => {
|
|
10
|
+
expect(true).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should initialize detector with LLM provider', async () => {
|
|
14
|
+
expect(true).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should detect dependencies in repository', async () => {
|
|
18
|
+
expect(true).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should create manifest structure', async () => {
|
|
22
|
+
// Expected: DependencyManifest with metadata
|
|
23
|
+
expect(true).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should write manifest to .dependabit/manifest.json', async () => {
|
|
27
|
+
expect(true).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should set action outputs', async () => {
|
|
31
|
+
// Expected: manifestPath, dependencyCount, statistics
|
|
32
|
+
expect(true).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should create summary report', async () => {
|
|
36
|
+
// Expected: Markdown summary with stats
|
|
37
|
+
expect(true).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle empty repository', async () => {
|
|
41
|
+
// Expected: Create valid but empty manifest
|
|
42
|
+
expect(true).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle LLM failures with informational issue', async () => {
|
|
46
|
+
// Expected: Create GitHub issue, continue with parser results
|
|
47
|
+
expect(true).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should log all LLM interactions', async () => {
|
|
51
|
+
// Expected: Structured logs with prompt, model, tokens, latency
|
|
52
|
+
expect(true).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should respect GITHUB_TOKEN for private repos', async () => {
|
|
56
|
+
expect(true).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should complete within 5 minutes for typical repo', async () => {
|
|
60
|
+
// Performance requirement from SC-001
|
|
61
|
+
expect(true).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('calculateStatistics', () => {
|
|
66
|
+
it('should count dependencies by type', () => {
|
|
67
|
+
expect(true).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should count dependencies by access method', () => {
|
|
71
|
+
expect(true).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should count dependencies by detection method', () => {
|
|
75
|
+
expect(true).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should calculate average confidence', () => {
|
|
79
|
+
expect(true).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import * as core from '@actions/core';
|
|
3
|
+
|
|
4
|
+
// Mock dependencies before importing the module under test
|
|
5
|
+
vi.mock('@actions/core');
|
|
6
|
+
vi.mock('@dependabit/github-client');
|
|
7
|
+
vi.mock('@dependabit/detector');
|
|
8
|
+
vi.mock('@dependabit/manifest');
|
|
9
|
+
|
|
10
|
+
const mockGetInput = vi.mocked(core.getInput);
|
|
11
|
+
|
|
12
|
+
describe('Update Action Integration Tests', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
|
|
16
|
+
// Set up default input mocks
|
|
17
|
+
mockGetInput.mockImplementation((name: string) => {
|
|
18
|
+
const inputs: Record<string, string> = {
|
|
19
|
+
action: 'update',
|
|
20
|
+
repo_path: '/test/repo',
|
|
21
|
+
manifest_path: '.dependabit/manifest.json',
|
|
22
|
+
commits: '',
|
|
23
|
+
llm_provider: 'github-copilot',
|
|
24
|
+
llm_api_key: 'test-token'
|
|
25
|
+
};
|
|
26
|
+
return inputs[name] || '';
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should process commits and update manifest', async () => {
|
|
31
|
+
// This test will be implemented after the update action is created
|
|
32
|
+
expect(true).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle multiple commits in a single push', async () => {
|
|
36
|
+
// Test for requirement: handle multiple commits pushed at once
|
|
37
|
+
expect(true).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should preserve manually added entries', async () => {
|
|
41
|
+
// Test for requirement: non-destructive merge
|
|
42
|
+
expect(true).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should detect new dependencies in commits', async () => {
|
|
46
|
+
// Test for requirement: analyze commits for added dependencies
|
|
47
|
+
expect(true).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should mark removed dependencies', async () => {
|
|
51
|
+
// Test for requirement: analyze commits for removed dependencies
|
|
52
|
+
expect(true).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle errors gracefully', async () => {
|
|
56
|
+
// Test error handling
|
|
57
|
+
expect(true).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should complete within time limit', async () => {
|
|
61
|
+
// Test for SC-002: complete within 2 minutes
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
|
|
64
|
+
// Simulate update action (will be implemented)
|
|
65
|
+
// await run();
|
|
66
|
+
|
|
67
|
+
const duration = Date.now() - startTime;
|
|
68
|
+
expect(duration).toBeLessThan(2 * 60 * 1000); // 2 minutes
|
|
69
|
+
});
|
|
70
|
+
});
|