Skip to content
Merged
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

# CHANGELOG

## [v3.2.65] - 2026-04-21 — New-CloudSchedule.ps1 ユニットテスト追加

### 🎯 概要
`scripts/main/New-CloudSchedule.ps1`(772行)に対するユニットテストがゼロだった問題を解消。`Build-CreatePrompt`(プロンプト文字列生成)と `New-LoopPreset`(4プリセット生成)を対象に 23 テストケースを追加。スクリプト(.ps1)を Pester から dot-source する際の `exit` 問題を `exit→return` パッチ戦略で解決。

### 🔧 変更対象
| ファイル | 変更内容 |
|---|---|
| `tests/unit/NewCloudSchedule.Tests.ps1` | 新規作成: `New-LoopPreset` 12件 + `Build-CreatePrompt` 11件 = 23 テストケース / UTF-8 BOM / PSScriptAnalyzer 0警告 |

### ✅ テスト結果
- Pester: 23/23 passed
- PSScriptAnalyzer 0 warnings (non-WriteHost)

---

## [v3.2.64] - 2026-04-21 — 管理操作(OFFA/ONA/DELA)をプロジェクトスコープに限定

### 🎯 概要
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@

| 項目 | 状態 |
|------|------|
| バージョン | **v3.2.64** (管理OFFA/ONA/DELA操作をプロジェクトスコープに限定) — 旧: v3.2.63 (PSScriptAnalyzer 0警告達成 / 一覧表示プロジェクト別フィルタ改善) |
| テスト | **680件** — Pester (Unit 17 / Integration 11 / Smoke 1) |
| バージョン | **v3.2.65** (New-CloudSchedule.ps1 ユニットテスト 23件追加) — 旧: v3.2.64 (管理OFFA/ONA/DELA操作をプロジェクトスコープに限定) |
| テスト | **703件** — Pester (Unit 18 / Integration 11 / Smoke 1) |
Comment on lines +30 to +31

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

README 内のテスト件数が不整合です

Line 31 は 703 件に更新されていますが、Line 96 / Line 231 / Line 507 / Line 538 などに旧値が残っており、README 単体で真値が判別しづらい状態です。

💡 修正案(同PR内で揃える)
- | **test** | `Invoke-Pester .\tests -CI` | Pester 全テスト(現在 680 件 / Unit + Integration + Smoke)。`-CI` で `testResults.xml` 生成 |
+ | **test** | `Invoke-Pester .\tests -CI` | Pester 全テスト(現在 703 件 / Unit + Integration + Smoke)。`-CI` で `testResults.xml` 生成 |

- | ⚡ CI/CD | 🔧 共通 | GitHub Actions による自動テスト (Pester 680件 — 29 test files) |
+ | ⚡ CI/CD | 🔧 共通 | GitHub Actions による自動テスト (Pester 703件 — 30 test files) |

- tests/               Pester テスト (29 files / 680件 — Unit 17 / Integration 11 / Smoke 1)
+ tests/               Pester テスト (30 files / 703件 — Unit 18 / Integration 11 / Smoke 1)

- # Pester テスト (433件 — Unit:311 / E2E:122)
+ # Pester テスト (703件 — Unit:18 / Integration:11 / Smoke:1)

As per coding guidelines **/*.md: maintain README as external-facing truth with clear organization, extensive use of tables and icons, and include architecture diagrams.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 30 - 31, The README shows inconsistent test counts:
update every occurrence of the old test total to the new value (703) so the
document is internally consistent; search for the table row with header "バージョン"
and the "テスト" row shown in the diff and replace any stale totals (e.g., older
numbers appearing elsewhere) with 703, and verify related breakdowns
(Unit/Integration/Smoke) remain correct or are updated to match the new total;
ensure the README's external-facing summary/table entries and any repeated
counts (including sections like the version row and other occurrences) are
synchronized in this PR.

| CI | ✅ SUCCESS |
| ClaudeOS (Claude Code 専用) | v8 (Opus 4.7 最適化 / Token 1.35x 補正 / Agent Teams 並列 spawn / `/compact` 事前発動 / `task_budget` / 1H cache / `/ultrareview` / PreCompact hook / `/recap` fallback / Push Notification / Effort 動的切替) |
| Agents | **25体** の特化サブエージェント (2026Q2 棚卸し後、追加復元済み) |
Expand Down
10 changes: 10 additions & 0 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
77. [DONE] [Priority:P1][Owner:Developer][Source:Manual] v3.2.62 Cron全同期をプロジェクト選択画面に移動・空URL/関数順序バグ修正 — Select-Project に [S] + while ループ / Invoke-CronAllSync を Select-Project より前に移動 / 空URL exit 0 ガード / Show-CloudScheduleMenu から [6] 削除 / CI SUCCESS (commit e5ee3a0, PR #222)
78. [DONE] [Priority:P2][Owner:Developer][Source:Manual] v3.2.63 PSScriptAnalyzer 0警告達成・一覧表示プロジェクト別フィルタ改善 — 空catchブロック $null=$_ / UTF-8 BOM / New-LoopPreset 改名 / SuppressMessage 関数内移動 / Watch-ClaudeLog $using: / Invoke-CloudList プロジェクトフィルタ
79. [DONE] [Priority:P1][Owner:Developer][Source:Manual] v3.2.64 管理操作(OFFA/ONA/DELA)をプロジェクトスコープに限定 — 全一括操作が全プロジェクトに影響する問題修正 / 確認ダイアログにプロジェクト名明示 / $script:RepoUrl フィルタ追加
80. [DONE] [Priority:P2][Owner:Developer][Source:GitHub#226] v3.2.65 New-CloudSchedule.ps1 ユニットテスト追加 — Build-CreatePrompt / New-LoopPreset の 23 テストケース / dot-source exit→return パッチ戦略 / PSScriptAnalyzer 0警告

## Auto Extracted From Agent Teams Matrix

Expand Down Expand Up @@ -174,6 +175,15 @@















Expand Down
180 changes: 180 additions & 0 deletions tests/unit/NewCloudSchedule.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# ============================================================
# NewCloudSchedule.Tests.ps1 - New-CloudSchedule.ps1 unit tests
# Pester 5.x / Issue #226
#
# Strategy: dot-source the script with exit->return patching so the
# Pester process is not terminated by the -NonInteractive exit 0.
# A fake claude.cmd stub is added to PATH to satisfy the CLI check.
# ============================================================

BeforeAll {
$script:RepoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$script:ScriptPath = Join-Path $script:RepoRoot 'scripts\main\New-CloudSchedule.ps1'

# --- Fake claude stub (satisfies the CLI-detection block) ---
$script:TempDir = Join-Path ([System.IO.Path]::GetTempPath()) 'pester-cloudschedule-tests'

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

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

一時ディレクトリが固定名 pester-cloudschedule-tests になっているため、並列実行や前回実行の残骸がある環境で衝突しやすいです。Pester の $TestDrive を使うか、$PID/GUID を含めたユニークなディレクトリ名にして競合を避けてください。

Suggested change
$script:TempDir = Join-Path ([System.IO.Path]::GetTempPath()) 'pester-cloudschedule-tests'
$uniqueTempDirName = 'pester-cloudschedule-tests-{0}-{1}' -f $PID, ([guid]::NewGuid().ToString('N'))
$script:TempDir = Join-Path ([System.IO.Path]::GetTempPath()) $uniqueTempDirName

Copilot uses AI. Check for mistakes.
$null = New-Item -ItemType Directory -Path $script:TempDir -Force
'@echo off' | Set-Content (Join-Path $script:TempDir 'claude.cmd') -Encoding ASCII
$script:SavedPath = $env:PATH
$env:PATH = "$script:TempDir;$env:PATH"

# --- Patch: replace 'exit <n>' with 'return' to prevent killing Pester ---
$raw = Get-Content $script:ScriptPath -Raw -Encoding UTF8
$patched = $raw -replace '(?m)^\s*exit\s+\d+\s*$', 'return'
$script:TempScript = Join-Path $script:TempDir 'NewCloudSchedule.tmp.ps1'
$patched | Set-Content $script:TempScript -Encoding UTF8
Comment on lines +15 to +25

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

一時ディレクトリ名が固定で並列実行時に競合します

$script:TempDir が固定値のため、同時実行時に stub/patch ファイルの上書きや削除競合でテストが不安定になります。ユニーク名にしてください。

💡 修正案
-    $script:TempDir   = Join-Path ([System.IO.Path]::GetTempPath()) 'pester-cloudschedule-tests'
+    $script:TempDir   = Join-Path ([System.IO.Path]::GetTempPath()) ("pester-cloudschedule-tests-{0}" -f [guid]::NewGuid().ToString('N'))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/NewCloudSchedule.Tests.ps1` around lines 15 - 25, The temp dir
name is fixed via $script:TempDir causing parallel test collisions; change how
$script:TempDir is constructed (and consequently $script:TempScript and the
claude.cmd path) to include a unique suffix (e.g., a GUID, timestamp, or
[System.IO.Path]::GetRandomFileName()) when calling Join-Path so each test run
gets its own directory, then create that directory as before and use the unique
path everywhere cleanup and file writes reference
$script:TempDir/$script:TempScript.


# Dot-source in NonInteractive mode: defines all functions, sets script: vars, then returns
. $script:TempScript -NonInteractive
Comment on lines +21 to +28

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

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

exit→return パッチの正規表現が「行頭が exit の行」しか置換しないため、New-CloudSchedule.ps1 側の if ($NonInteractive) { exit 0 } / ... { exit 0 } のようなインライン exit が残ります。BeforeAll で -NonInteractive を渡して dot-source しているので、このままだとテスト実行プロセス自体が exit で終了します(Pester が落ちる)。インライン exit も確実に無効化できる置換(例: より広いパターン、AST で ExitStatement を差し替え等)にしてください。

Copilot uses AI. Check for mistakes.
}

AfterAll {
$env:PATH = $script:SavedPath
if (Test-Path $script:TempDir) { Remove-Item $script:TempDir -Recurse -Force -ErrorAction SilentlyContinue }
}

# ============================================================
# New-LoopPreset
# ============================================================
Describe 'New-LoopPreset' {

It 'returns exactly 4 presets' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
$result.Count | Should -Be 4
}

It 'preset 0 is named ClaudeOS Monitor' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
$result[0].Name | Should -Be 'ClaudeOS Monitor'
}

It 'Monitor cron is hourly on Mon-Sat (0 * * * 1-6)' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
$result[0].Cron | Should -Be '0 * * * 1-6'
}

It 'preset 1 is named ClaudeOS Development' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
$result[1].Name | Should -Be 'ClaudeOS Development'
}

It 'Development cron is every 2 hours on Mon-Sat (0 */2 * * 1-6)' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
$result[1].Cron | Should -Be '0 */2 * * 1-6'
}

It 'preset 2 is named ClaudeOS Verify with hourly cron' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
$result[2].Name | Should -Be 'ClaudeOS Verify'
$result[2].Cron | Should -Be '0 * * * 1-6'
}

It 'preset 3 is named ClaudeOS Improvement with hourly cron' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
$result[3].Name | Should -Be 'ClaudeOS Improvement'
$result[3].Cron | Should -Be '0 * * * 1-6'
}

It 'injects repository URL into every Content field' {
$url = 'https://github.com/TestOrg/my-special-repo'
$urlPattern = [regex]::Escape($url)
$result = New-LoopPreset -Url $url
foreach ($preset in $result) {
$preset.Content | Should -Match $urlPattern
}
}

It 'each preset has Label, Name, Cron, Content properties' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
foreach ($preset in $result) {
$preset.PSObject.Properties.Name | Should -Contain 'Label'
$preset.PSObject.Properties.Name | Should -Contain 'Name'
$preset.PSObject.Properties.Name | Should -Contain 'Cron'
$preset.PSObject.Properties.Name | Should -Contain 'Content'
}
}

It 'all Content values are non-empty' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
foreach ($preset in $result) {
$preset.Content.Trim() | Should -Not -BeNullOrEmpty
}
}

It 'each preset Label is non-empty' {
$result = New-LoopPreset -Url 'https://github.com/test/repo'
foreach ($preset in $result) {
$preset.Label | Should -Not -BeNullOrEmpty
}
}

It 'accepts different repository URLs without error' {
{ New-LoopPreset -Url 'https://github.com/another-org/another-repo' } | Should -Not -Throw
}
}

# ============================================================
# Build-CreatePrompt
# ============================================================
Describe 'Build-CreatePrompt' {

It 'contains RemoteTrigger creation instruction' {
$result = Build-CreatePrompt -Name 'TestTrigger' -Cron '0 * * * 1-6' -PromptContent 'hello'
$result | Should -Match 'RemoteTrigger'
}

It 'embeds Name in prompt output' {
$result = Build-CreatePrompt -Name 'ClaudeOS Monitor' -Cron '0 * * * 1-6' -PromptContent 'content'
$result | Should -Match '"ClaudeOS Monitor"'
}

It 'embeds Cron expression in prompt output' {
$cronPattern = [regex]::Escape('"0 */2 * * 1-6"')
$result = Build-CreatePrompt -Name 'T' -Cron '0 */2 * * 1-6' -PromptContent 'content'
$result | Should -Match $cronPattern
}

It 'sets enabled: true' {
$result = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent 'x'
$result | Should -Match 'enabled: true'
}

It 'sets model to claude-sonnet-4-6' {
$result = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent 'x'
$result | Should -Match 'claude-sonnet-4-6'
}

It 'escapes double-quotes in PromptContent' {
$result = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent 'say "hello" now'
$result | Should -Match '\\"hello\\"'
}

It 'escapes newlines in PromptContent as literal \n' {
$result = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent "line1`nline2"
# Content field should contain literal \n (2 chars), not a real newline
$result | Should -Match 'line1\\nline2'
}

It 'generates a UUID (RFC 4122 format)' {
$result = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent 'x'
$result | Should -Match '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
}

It 'generates a different UUID on each call' {
$r1 = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent 'x'
$r2 = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent 'x'
$uuid1 = [regex]::Match($r1, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}').Value
$uuid2 = [regex]::Match($r2, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}').Value
$uuid1 | Should -Not -Be $uuid2
}

It 'includes CREATED_ID output instruction' {
$result = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent 'x'
$result | Should -Match 'CREATED_ID='
}

It 'includes git_repository source URL' {
$result = Build-CreatePrompt -Name 'T' -Cron '0 * * * 1-6' -PromptContent 'x'
$result | Should -Match 'git_repository'
}
}
Loading