@sk8metal/michi-cli 0.4.0 → 0.5.0
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 +40 -0
- package/dist/scripts/__tests__/spec-impl-workflow.test.js +4 -2
- package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +1 -1
- package/dist/scripts/config/config-schema.d.ts +52 -0
- package/dist/scripts/config/config-schema.d.ts.map +1 -1
- package/dist/scripts/config/config-schema.js +25 -0
- package/dist/scripts/config/config-schema.js.map +1 -1
- package/dist/scripts/pr-automation.d.ts.map +1 -1
- package/dist/scripts/pr-automation.js +11 -3
- package/dist/scripts/pr-automation.js.map +1 -1
- package/dist/scripts/spec-impl-workflow.d.ts.map +1 -1
- package/dist/scripts/spec-impl-workflow.js +22 -6
- package/dist/scripts/spec-impl-workflow.js.map +1 -1
- package/dist/scripts/utils/__tests__/config-loader.test.js +114 -1
- package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/config-validator.test.js +2 -0
- package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/env-config.test.js +0 -2
- package/dist/scripts/utils/__tests__/env-config.test.js.map +1 -1
- package/dist/scripts/utils/__tests__/project-meta.test.d.ts +6 -0
- package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/project-meta.test.js +154 -0
- package/dist/scripts/utils/__tests__/project-meta.test.js.map +1 -0
- package/dist/scripts/utils/__tests__/security-validator.test.d.ts +6 -0
- package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +1 -0
- package/dist/scripts/utils/__tests__/security-validator.test.js +219 -0
- package/dist/scripts/utils/__tests__/security-validator.test.js.map +1 -0
- package/dist/scripts/utils/config-loader.d.ts +10 -4
- package/dist/scripts/utils/config-loader.d.ts.map +1 -1
- package/dist/scripts/utils/config-loader.js +214 -47
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/env-config.d.ts +1 -1
- package/dist/scripts/utils/env-config.d.ts.map +1 -1
- package/dist/scripts/utils/env-config.js +2 -14
- package/dist/scripts/utils/env-config.js.map +1 -1
- package/dist/scripts/utils/project-meta.d.ts +9 -0
- package/dist/scripts/utils/project-meta.d.ts.map +1 -1
- package/dist/scripts/utils/project-meta.js +22 -0
- package/dist/scripts/utils/project-meta.js.map +1 -1
- package/dist/scripts/utils/security-validator.d.ts +55 -0
- package/dist/scripts/utils/security-validator.d.ts.map +1 -0
- package/dist/scripts/utils/security-validator.js +232 -0
- package/dist/scripts/utils/security-validator.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +41 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/__tests__/init.test.d.ts +5 -0
- package/dist/src/commands/__tests__/init.test.d.ts.map +1 -0
- package/dist/src/commands/__tests__/init.test.js +255 -0
- package/dist/src/commands/__tests__/init.test.js.map +1 -0
- package/dist/src/commands/__tests__/migrate.test.d.ts +5 -0
- package/dist/src/commands/__tests__/migrate.test.d.ts.map +1 -0
- package/dist/src/commands/__tests__/migrate.test.js +216 -0
- package/dist/src/commands/__tests__/migrate.test.js.map +1 -0
- package/dist/src/commands/config-validate.d.ts +9 -0
- package/dist/src/commands/config-validate.d.ts.map +1 -0
- package/dist/src/commands/config-validate.js +90 -0
- package/dist/src/commands/config-validate.js.map +1 -0
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +29 -6
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/migrate.d.ts +25 -0
- package/dist/src/commands/migrate.d.ts.map +1 -0
- package/dist/src/commands/migrate.js +341 -0
- package/dist/src/commands/migrate.js.map +1 -0
- package/dist/src/commands/setup-existing.d.ts.map +1 -1
- package/dist/src/commands/setup-existing.js +0 -1
- package/dist/src/commands/setup-existing.js.map +1 -1
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +32 -8
- package/dist/vitest.config.js.map +1 -1
- package/docs/michi-development/design/config-unification.md +4789 -0
- package/docs/user-guide/getting-started/github-token-setup.md +2 -1
- package/docs/user-guide/getting-started/new-repository-setup.md +1 -1
- package/docs/user-guide/getting-started/quick-start.md +1 -1
- package/docs/user-guide/getting-started/setup.md +4 -11
- package/docs/user-guide/hands-on/claude-agent-setup.md +2 -2
- package/docs/user-guide/hands-on/claude-setup.md +2 -2
- package/docs/user-guide/hands-on/cursor-setup.md +2 -2
- package/docs/user-guide/hands-on/workflow-walkthrough.md +4 -1
- package/env.example +1 -1
- package/package.json +2 -2
- package/scripts/__tests__/spec-impl-workflow.test.ts +5 -2
- package/scripts/config/config-schema.ts +40 -0
- package/scripts/pr-automation.ts +15 -5
- package/scripts/spec-impl-workflow.ts +22 -6
- package/scripts/utils/__tests__/config-loader.test.ts +149 -0
- package/scripts/utils/__tests__/config-validator.test.ts +2 -0
- package/scripts/utils/__tests__/env-config.test.ts +0 -2
- package/scripts/utils/__tests__/project-meta.test.ts +192 -0
- package/scripts/utils/__tests__/security-validator.test.ts +272 -0
- package/scripts/utils/config-loader.ts +232 -53
- package/scripts/utils/env-config.ts +2 -14
- package/scripts/utils/project-meta.ts +27 -0
- package/scripts/utils/security-validator.ts +286 -0
- package/templates/claude/commands/kiro/kiro-spec-impl.md +1 -1
- package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +1 -1
- package/templates/cursor/commands/kiro/kiro-spec-impl.md +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* project-meta.test.ts
|
|
3
|
+
* プロジェクトメタデータユーティリティのテスト
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { loadProjectMeta, getRepositoryInfo } from '../project-meta.js';
|
|
10
|
+
|
|
11
|
+
describe('project-meta', () => {
|
|
12
|
+
const testRoot = join(process.cwd(), '.test-tmp-project-meta');
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
if (existsSync(testRoot)) {
|
|
16
|
+
rmSync(testRoot, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
mkdirSync(testRoot, { recursive: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
if (existsSync(testRoot)) {
|
|
23
|
+
rmSync(testRoot, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('loadProjectMeta', () => {
|
|
28
|
+
it('正しいproject.jsonを読み込める', () => {
|
|
29
|
+
const kiroDir = join(testRoot, '.kiro');
|
|
30
|
+
mkdirSync(kiroDir, { recursive: true });
|
|
31
|
+
|
|
32
|
+
const projectMeta = {
|
|
33
|
+
projectId: 'test-project',
|
|
34
|
+
projectName: 'Test Project',
|
|
35
|
+
jiraProjectKey: 'TEST',
|
|
36
|
+
confluenceLabels: ['label1', 'label2'],
|
|
37
|
+
status: 'active' as const,
|
|
38
|
+
team: ['member1'],
|
|
39
|
+
stakeholders: ['stakeholder1'],
|
|
40
|
+
repository: 'https://github.com/owner/repo.git',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
writeFileSync(join(kiroDir, 'project.json'), JSON.stringify(projectMeta, null, 2));
|
|
44
|
+
|
|
45
|
+
const result = loadProjectMeta(testRoot);
|
|
46
|
+
|
|
47
|
+
expect(result).toEqual(projectMeta);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('project.jsonが存在しない場合はエラー', () => {
|
|
51
|
+
expect(() => loadProjectMeta(testRoot)).toThrow('Project metadata not found');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('.kiroディレクトリが存在しない場合はエラー', () => {
|
|
55
|
+
expect(() => loadProjectMeta(testRoot)).toThrow('Project metadata not found');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('不正なJSON形式の場合はエラー', () => {
|
|
59
|
+
const kiroDir = join(testRoot, '.kiro');
|
|
60
|
+
mkdirSync(kiroDir, { recursive: true });
|
|
61
|
+
writeFileSync(join(kiroDir, 'project.json'), 'invalid json');
|
|
62
|
+
|
|
63
|
+
expect(() => loadProjectMeta(testRoot)).toThrow('Invalid JSON');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('必須フィールド projectName が欠けている場合はエラー', () => {
|
|
67
|
+
const kiroDir = join(testRoot, '.kiro');
|
|
68
|
+
mkdirSync(kiroDir, { recursive: true });
|
|
69
|
+
|
|
70
|
+
const invalidMeta = {
|
|
71
|
+
projectId: 'test-project',
|
|
72
|
+
// projectName missing
|
|
73
|
+
jiraProjectKey: 'TEST',
|
|
74
|
+
confluenceLabels: ['label1'],
|
|
75
|
+
status: 'active',
|
|
76
|
+
team: [],
|
|
77
|
+
stakeholders: [],
|
|
78
|
+
repository: 'https://github.com/owner/repo.git',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
writeFileSync(join(kiroDir, 'project.json'), JSON.stringify(invalidMeta));
|
|
82
|
+
|
|
83
|
+
expect(() => loadProjectMeta(testRoot)).toThrow('Required field missing');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('必須フィールド confluenceLabels が欠けている場合はエラー', () => {
|
|
87
|
+
const kiroDir = join(testRoot, '.kiro');
|
|
88
|
+
mkdirSync(kiroDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
const invalidMeta = {
|
|
91
|
+
projectId: 'test-project',
|
|
92
|
+
projectName: 'Test Project',
|
|
93
|
+
jiraProjectKey: 'TEST',
|
|
94
|
+
// confluenceLabels missing
|
|
95
|
+
status: 'active',
|
|
96
|
+
team: [],
|
|
97
|
+
stakeholders: [],
|
|
98
|
+
repository: 'https://github.com/owner/repo.git',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
writeFileSync(join(kiroDir, 'project.json'), JSON.stringify(invalidMeta));
|
|
102
|
+
|
|
103
|
+
expect(() => loadProjectMeta(testRoot)).toThrow('Required field missing');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('getRepositoryInfo', () => {
|
|
108
|
+
const createValidProjectMeta = (repository: string) => ({
|
|
109
|
+
projectId: 'test-project',
|
|
110
|
+
projectName: 'Test Project',
|
|
111
|
+
jiraProjectKey: 'TEST',
|
|
112
|
+
confluenceLabels: ['label1'],
|
|
113
|
+
status: 'active' as const,
|
|
114
|
+
team: [],
|
|
115
|
+
stakeholders: [],
|
|
116
|
+
repository,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
const kiroDir = join(testRoot, '.kiro');
|
|
121
|
+
mkdirSync(kiroDir, { recursive: true });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('HTTPS URL (.git付き) から owner/repo を抽出できる', () => {
|
|
125
|
+
const projectMeta = createValidProjectMeta('https://github.com/owner/repo.git');
|
|
126
|
+
writeFileSync(join(testRoot, '.kiro/project.json'), JSON.stringify(projectMeta));
|
|
127
|
+
|
|
128
|
+
const result = getRepositoryInfo(testRoot);
|
|
129
|
+
expect(result).toBe('owner/repo');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('HTTPS URL (.gitなし) から owner/repo を抽出できる', () => {
|
|
133
|
+
const projectMeta = createValidProjectMeta('https://github.com/owner/repo');
|
|
134
|
+
writeFileSync(join(testRoot, '.kiro/project.json'), JSON.stringify(projectMeta));
|
|
135
|
+
|
|
136
|
+
const result = getRepositoryInfo(testRoot);
|
|
137
|
+
expect(result).toBe('owner/repo');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('SSH URL (.git付き) から owner/repo を抽出できる', () => {
|
|
141
|
+
const projectMeta = createValidProjectMeta('git@github.com:owner/repo.git');
|
|
142
|
+
writeFileSync(join(testRoot, '.kiro/project.json'), JSON.stringify(projectMeta));
|
|
143
|
+
|
|
144
|
+
const result = getRepositoryInfo(testRoot);
|
|
145
|
+
expect(result).toBe('owner/repo');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('SSH URL (.gitなし) から owner/repo を抽出できる', () => {
|
|
149
|
+
const projectMeta = createValidProjectMeta('git@github.com:owner/repo');
|
|
150
|
+
writeFileSync(join(testRoot, '.kiro/project.json'), JSON.stringify(projectMeta));
|
|
151
|
+
|
|
152
|
+
const result = getRepositoryInfo(testRoot);
|
|
153
|
+
expect(result).toBe('owner/repo');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('repository フィールドが存在しない場合はエラー', () => {
|
|
157
|
+
const projectMeta = {
|
|
158
|
+
projectId: 'test-project',
|
|
159
|
+
projectName: 'Test Project',
|
|
160
|
+
jiraProjectKey: 'TEST',
|
|
161
|
+
confluenceLabels: ['label1'],
|
|
162
|
+
status: 'active' as const,
|
|
163
|
+
team: [],
|
|
164
|
+
stakeholders: [],
|
|
165
|
+
repository: '',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
writeFileSync(join(testRoot, '.kiro/project.json'), JSON.stringify(projectMeta));
|
|
169
|
+
|
|
170
|
+
expect(() => getRepositoryInfo(testRoot)).toThrow('Repository information not found');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('不正な repository 形式の場合はエラー', () => {
|
|
174
|
+
const projectMeta = createValidProjectMeta('https://example.com/not-github/repo.git');
|
|
175
|
+
writeFileSync(join(testRoot, '.kiro/project.json'), JSON.stringify(projectMeta));
|
|
176
|
+
|
|
177
|
+
expect(() => getRepositoryInfo(testRoot)).toThrow('Invalid GitHub repository format');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('owner/repo にハイフンが含まれる場合も正しく抽出できる', () => {
|
|
181
|
+
const projectMeta = createValidProjectMeta('https://github.com/my-org/my-repo.git');
|
|
182
|
+
writeFileSync(join(testRoot, '.kiro/project.json'), JSON.stringify(projectMeta));
|
|
183
|
+
|
|
184
|
+
const result = getRepositoryInfo(testRoot);
|
|
185
|
+
expect(result).toBe('my-org/my-repo');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('project.jsonが存在しない場合はエラー', () => {
|
|
189
|
+
expect(() => getRepositoryInfo(testRoot)).toThrow('Project metadata not found');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* security-validator.test.ts
|
|
3
|
+
* セキュリティバリデーターのテスト
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
validateAtlassianToken,
|
|
9
|
+
validateGitHubToken,
|
|
10
|
+
validateEmail,
|
|
11
|
+
validateUrl,
|
|
12
|
+
validateAtlassianUrl,
|
|
13
|
+
validateGitHubRepositoryUrl,
|
|
14
|
+
validateEnvironmentConfig,
|
|
15
|
+
} from '../security-validator.js';
|
|
16
|
+
|
|
17
|
+
describe('security-validator', () => {
|
|
18
|
+
describe('validateAtlassianToken', () => {
|
|
19
|
+
it('空のトークンはエラー', () => {
|
|
20
|
+
const result = validateAtlassianToken('');
|
|
21
|
+
expect(result.isValid).toBe(false);
|
|
22
|
+
expect(result.errors).toContain('Atlassian API token is empty');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('スペースを含むトークンはエラー', () => {
|
|
26
|
+
const result = validateAtlassianToken('token with spaces');
|
|
27
|
+
expect(result.isValid).toBe(false);
|
|
28
|
+
expect(result.errors).toContain('Atlassian API token contains spaces');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('短すぎるトークンは警告', () => {
|
|
32
|
+
const result = validateAtlassianToken('short');
|
|
33
|
+
expect(result.isValid).toBe(true);
|
|
34
|
+
expect(result.warnings).toContain(
|
|
35
|
+
'Atlassian API token seems too short (expected >20 characters)',
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('プレースホルダー値はエラー', () => {
|
|
40
|
+
const result = validateAtlassianToken('your-token-here');
|
|
41
|
+
expect(result.isValid).toBe(false);
|
|
42
|
+
expect(result.errors).toContain('Atlassian API token is a placeholder value');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('正しいトークンは成功', () => {
|
|
46
|
+
const result = validateAtlassianToken('ATATT3xFfGF0abcdefghijklmnopqrstuvwxyz1234567890');
|
|
47
|
+
expect(result.isValid).toBe(true);
|
|
48
|
+
expect(result.errors).toHaveLength(0);
|
|
49
|
+
expect(result.warnings).toHaveLength(0);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('validateGitHubToken', () => {
|
|
54
|
+
it('空のトークンはエラー', () => {
|
|
55
|
+
const result = validateGitHubToken('');
|
|
56
|
+
expect(result.isValid).toBe(false);
|
|
57
|
+
expect(result.errors).toContain('GitHub token is empty');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('スペースを含むトークンはエラー', () => {
|
|
61
|
+
const result = validateGitHubToken('token with spaces');
|
|
62
|
+
expect(result.isValid).toBe(false);
|
|
63
|
+
expect(result.errors).toContain('GitHub token contains spaces');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('プレフィックスなしのトークンは警告', () => {
|
|
67
|
+
const result = validateGitHubToken('abcdefghijklmnopqrstuvwxyz1234567890');
|
|
68
|
+
expect(result.isValid).toBe(true);
|
|
69
|
+
expect(result.warnings).toContain(
|
|
70
|
+
'GitHub token does not start with expected prefix (ghp_ or github_pat_)',
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('プレースホルダー値はエラー', () => {
|
|
75
|
+
const result = validateGitHubToken('ghp_xxx');
|
|
76
|
+
expect(result.isValid).toBe(false);
|
|
77
|
+
expect(result.errors).toContain('GitHub token is a placeholder value');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('正しいトークン(ghp_)は成功', () => {
|
|
81
|
+
const result = validateGitHubToken('ghp_abcdefghijklmnopqrstuvwxyz1234567890');
|
|
82
|
+
expect(result.isValid).toBe(true);
|
|
83
|
+
expect(result.errors).toHaveLength(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('正しいトークン(github_pat_)は成功', () => {
|
|
87
|
+
const result = validateGitHubToken('github_pat_abcdefghijklmnopqrstuvwxyz1234567890');
|
|
88
|
+
expect(result.isValid).toBe(true);
|
|
89
|
+
expect(result.errors).toHaveLength(0);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('validateEmail', () => {
|
|
94
|
+
it('空のメールアドレスはエラー', () => {
|
|
95
|
+
const result = validateEmail('');
|
|
96
|
+
expect(result.isValid).toBe(false);
|
|
97
|
+
expect(result.errors).toContain('Email is empty');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('不正な形式のメールアドレスはエラー', () => {
|
|
101
|
+
const result = validateEmail('invalid-email');
|
|
102
|
+
expect(result.isValid).toBe(false);
|
|
103
|
+
expect(result.errors).toContain('Email format is invalid');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('プレースホルダー値はエラー', () => {
|
|
107
|
+
const result = validateEmail('user@example.com');
|
|
108
|
+
expect(result.isValid).toBe(false);
|
|
109
|
+
expect(result.errors).toContain('Email is a placeholder value');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('正しいメールアドレスは成功', () => {
|
|
113
|
+
const result = validateEmail('real.user@company.com');
|
|
114
|
+
expect(result.isValid).toBe(true);
|
|
115
|
+
expect(result.errors).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('validateUrl', () => {
|
|
120
|
+
it('空のURLはエラー', () => {
|
|
121
|
+
const result = validateUrl('');
|
|
122
|
+
expect(result.isValid).toBe(false);
|
|
123
|
+
expect(result.errors).toContain('URL is empty');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('不正な形式のURLはエラー', () => {
|
|
127
|
+
const result = validateUrl('not-a-url');
|
|
128
|
+
expect(result.isValid).toBe(false);
|
|
129
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('httpプロトコルは警告', () => {
|
|
133
|
+
const result = validateUrl('http://example.com');
|
|
134
|
+
expect(result.isValid).toBe(true);
|
|
135
|
+
expect(result.warnings).toContain(
|
|
136
|
+
'URL uses insecure http:// protocol (consider using https://)',
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('プレースホルダー値はエラー', () => {
|
|
141
|
+
const result = validateUrl('https://example.com');
|
|
142
|
+
expect(result.isValid).toBe(false);
|
|
143
|
+
expect(result.errors).toContain('URL is a placeholder value');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('正しいURLは成功', () => {
|
|
147
|
+
const result = validateUrl('https://real-domain.com');
|
|
148
|
+
expect(result.isValid).toBe(true);
|
|
149
|
+
expect(result.errors).toHaveLength(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('expectedDomainが一致しない場合はエラー', () => {
|
|
153
|
+
const result = validateUrl('https://wrong.com', 'example.com');
|
|
154
|
+
expect(result.isValid).toBe(false);
|
|
155
|
+
expect(result.errors).toContain(
|
|
156
|
+
'URL hostname wrong.com does not contain expected domain: example.com',
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('expectedDomainが一致する場合は成功', () => {
|
|
161
|
+
const result = validateUrl('https://sub.example.com', 'example.com');
|
|
162
|
+
expect(result.isValid).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('validateAtlassianUrl', () => {
|
|
167
|
+
it('atlassian.netドメインでない場合はエラー', () => {
|
|
168
|
+
const result = validateAtlassianUrl('https://wrong-domain.com');
|
|
169
|
+
expect(result.isValid).toBe(false);
|
|
170
|
+
expect(result.errors.some((e) => e.includes('atlassian.net'))).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('正しいAtlassian URLは成功', () => {
|
|
174
|
+
const result = validateAtlassianUrl('https://mycompany.atlassian.net');
|
|
175
|
+
expect(result.isValid).toBe(true);
|
|
176
|
+
expect(result.errors).toHaveLength(0);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('validateGitHubRepositoryUrl', () => {
|
|
181
|
+
it('空のURLはエラー', () => {
|
|
182
|
+
const result = validateGitHubRepositoryUrl('');
|
|
183
|
+
expect(result.isValid).toBe(false);
|
|
184
|
+
expect(result.errors).toContain('Repository URL is empty');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('不正な形式はエラー', () => {
|
|
188
|
+
const result = validateGitHubRepositoryUrl('https://example.com/repo');
|
|
189
|
+
expect(result.isValid).toBe(false);
|
|
190
|
+
expect(result.errors.some((e) => e.includes('format'))).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('プレースホルダー値はエラー', () => {
|
|
194
|
+
const result = validateGitHubRepositoryUrl('https://github.com/your-org/your-repo.git');
|
|
195
|
+
expect(result.isValid).toBe(false);
|
|
196
|
+
expect(result.errors).toContain('Repository URL contains placeholder values');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('正しいHTTPS URLは成功', () => {
|
|
200
|
+
const result = validateGitHubRepositoryUrl('https://github.com/owner/repo.git');
|
|
201
|
+
expect(result.isValid).toBe(true);
|
|
202
|
+
expect(result.errors).toHaveLength(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('正しいHTTPS URL(.gitなし)も成功', () => {
|
|
206
|
+
const result = validateGitHubRepositoryUrl('https://github.com/owner/repo');
|
|
207
|
+
expect(result.isValid).toBe(true);
|
|
208
|
+
expect(result.errors).toHaveLength(0);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('正しいSSH URLは成功', () => {
|
|
212
|
+
const result = validateGitHubRepositoryUrl('git@github.com:owner/repo.git');
|
|
213
|
+
expect(result.isValid).toBe(true);
|
|
214
|
+
expect(result.errors).toHaveLength(0);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('正しいSSH URL(.gitなし)も成功', () => {
|
|
218
|
+
const result = validateGitHubRepositoryUrl('git@github.com:owner/repo');
|
|
219
|
+
expect(result.isValid).toBe(true);
|
|
220
|
+
expect(result.errors).toHaveLength(0);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('validateEnvironmentConfig', () => {
|
|
225
|
+
it('すべて正しい設定は成功', () => {
|
|
226
|
+
const result = validateEnvironmentConfig({
|
|
227
|
+
atlassianUrl: 'https://mycompany.atlassian.net',
|
|
228
|
+
atlassianEmail: 'user@company.com',
|
|
229
|
+
atlassianApiToken: 'ATATT3xFfGF0abcdefghijklmnopqrstuvwxyz1234567890',
|
|
230
|
+
githubOrg: 'myorg',
|
|
231
|
+
githubToken: 'ghp_abcdefghijklmnopqrstuvwxyz1234567890',
|
|
232
|
+
repositoryUrl: 'https://github.com/myorg/myrepo.git',
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(result.isValid).toBe(true);
|
|
236
|
+
expect(result.errors).toHaveLength(0);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('複数のエラーをすべて報告', () => {
|
|
240
|
+
const result = validateEnvironmentConfig({
|
|
241
|
+
atlassianUrl: 'https://example.com', // プレースホルダー
|
|
242
|
+
atlassianEmail: 'invalid-email', // 不正な形式
|
|
243
|
+
atlassianApiToken: 'test-token', // プレースホルダー
|
|
244
|
+
githubOrg: 'your-org', // プレースホルダー
|
|
245
|
+
githubToken: 'ghp_xxx', // プレースホルダー
|
|
246
|
+
repositoryUrl: '', // 空
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(result.isValid).toBe(false);
|
|
250
|
+
expect(result.errors.length).toBeGreaterThan(5);
|
|
251
|
+
expect(result.errors.some((e) => e.startsWith('ATLASSIAN_URL:'))).toBe(true);
|
|
252
|
+
expect(result.errors.some((e) => e.startsWith('ATLASSIAN_EMAIL:'))).toBe(true);
|
|
253
|
+
expect(result.errors.some((e) => e.startsWith('ATLASSIAN_API_TOKEN:'))).toBe(true);
|
|
254
|
+
expect(result.errors.some((e) => e.startsWith('GITHUB_ORG:'))).toBe(true);
|
|
255
|
+
expect(result.errors.some((e) => e.startsWith('GITHUB_TOKEN:'))).toBe(true);
|
|
256
|
+
expect(result.errors.some((e) => e.startsWith('repository:'))).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('警告も正しく集約される', () => {
|
|
260
|
+
const result = validateEnvironmentConfig({
|
|
261
|
+
atlassianUrl: 'http://mycompany.atlassian.net', // http警告
|
|
262
|
+
atlassianApiToken: 'shorttoken', // 短い警告
|
|
263
|
+
githubToken: 'abcdefghijklmnopqrstuvwxyz1234567890', // プレフィックスなし警告
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
267
|
+
expect(result.warnings.some((w) => w.includes('ATLASSIAN_URL:'))).toBe(true);
|
|
268
|
+
expect(result.warnings.some((w) => w.includes('ATLASSIAN_API_TOKEN:'))).toBe(true);
|
|
269
|
+
expect(result.warnings.some((w) => w.includes('GITHUB_TOKEN:'))).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
});
|