Skip to content

add tests - #14

Merged
nett00n merged 15 commits into
mainfrom
feature/add-unit-tests
Sep 23, 2025
Merged

nett00n merged 15 commits into
mainfrom
feature/add-unit-tests

Conversation

@nett00n

@nett00n nett00n commented Sep 19, 2025 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Non-Windows builds now show a clear unsupported-platform message; release artifacts include generated SHA-256 checksums.
  • CI/CD

    • Added comprehensive pipelines for testing, build & publish (artifact uploads, optional VirusTotal), security scans, dependency/license review, and Codecov enforcement with coverage targets.
  • Tests

    • Large expansion of unit, integration, and coverage tests (Windows-focused and cross-platform) to improve reliability.
  • Documentation

    • New contributor and testing guidance; README badges added.
  • Chores

    • Dependabot cadence/metadata improved; linter, pre-commit, and ignore rules updated; CI configs tuned.

@coderabbitai

coderabbitai Bot commented Sep 19, 2025 •

Copy link
Copy Markdown

Walkthrough

Adds CI/CD configurations (test, build, security, dependency review, Codecov, Dependabot), introduces Windows build tags and non-Windows stubs, converts internal control flow to context-driven handlers, and adds a large suite of cross-platform and Windows-gated tests plus docs, gitignore, and linting config.

Changes

Cohort / File(s) Summary
CI Workflows
.github/workflows/test.yml, .github/workflows/build-and-publish.yml, .github/workflows/security.yml, .github/workflows/dependency-review.yml
New/updated GitHub Actions: Test Suite (fmt, lint, cross-platform tests, coverage + Codecov), Build & Publish (caching, go mod download/go mod verify, artifact verification, checksums, VT scan gated), Security Scan (govulncheck, CodeQL, static analysis), Dependency Review + license reporting.
Dependabot / Codecov / Lint / Pre-commit
.github/dependabot.yml, .codecov.yml, .golangci.yml, .pre-commit-config.yaml
Dependabot expanded for Go and GitHub Actions (schedule, reviewers, assignees, labels, PR limits, rebase); added .codecov.yml (coverage/status/comment/flags settings); basic GolangCI-Lint config; removed detect-aws-credentials pre-commit hook.
Repository & Docs
.gitignore, README.md, TESTING.md, CLAUDE.md
.gitignore updates (dotfiles, coverage outputs, exceptions); README badges added; new TESTING.md and CLAUDE.md documentation added.
Windows Build Gating (sources)
autorun.go, logging.go, main.go, theme.go, tray_handlers.go, updater.go, utils.go
Added Windows build tags (//go:build windows + legacy // +build windows); logging gained panic-recovery wrappers; main/updater/tray_handlers refactored to use context-aware variants.
Non-Windows stubs & exported App
main_stub.go, app_stub.go, updater_stub.go, utils_stub.go
Added non-Windows stubs: main_stub.go (exits), app_stub.go (exported App, NewApp(version), Run() returning unsupported error), updater_stub.go and utils_stub.go providing deterministic non-Windows behavior.
Context-driven control flow (internals)
main.go, tray_handlers.go, updater.go, logging.go
Introduced context-aware internals: startEventHandlersWithContext, handleMenuItemClicksWithContext, checkForUpdateWithContext, startUpdateClickHandlerWithContext; loops now honor ctx.Done(); tooltip/set reset wrapped with recovery.
Cross-platform & pure tests
cross_platform_test.go, pure_funcs_test.go, semver_utils_test.go, semver_test.go
New cross-platform and pure-function tests for version helpers, padding, and max; appropriate build tags used.
Unit tests (app, UI, updater, utils, icon)
app_test.go, ui_texts_test.go, updater_logic_test.go, utils_test.go, icon_test.go
Unit tests added: NewApp behavior, UI text constants, updater string/URL/command logic, utilities (getExePath, openBrowser, registry helpers), and icon embedding checks.
Large Windows-gated test suites
autorun_test.go, logging_test.go, theme_test.go, tray_handlers_test.go, updater_test.go, *coverage_test.go, systray_integration_test.go, systray_functional_test.go, windows_native_test.go, ...
Many new Windows-only tests exercising registry/theme/autorun, systray integration/functional flows, event-log behavior, updater interactions (httptest), and broad coverage scaffolding; many tests use t.Skip or environment guards.
Build / Release tweaks
.github/workflows/build-and-publish.yml
Replaced go mod tidy with go mod download + go mod verify, added Go module/build caching, artifact wildcard publishing, executable verification, SHA-256 checksum generation and upload, and conditional VirusTotal scan.
Minor header/format edits
icon.go, semver.go, ui_texts.go, semver_test.go
Added top-of-file header comments or blank-line adjustments; no logic changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Developer
  participant GitHub as "GitHub"
  participant Test as "Test Suite\n(.github/workflows/test.yml)"
  participant Build as "Build & Publish\n(.github/workflows/build-and-publish.yml)"
  Developer->>GitHub: push / open PR
  GitHub->>Test: run Test Suite (fmt, lint, tests, coverage + Codecov)
  Test-->>GitHub: status + coverage artifact
  alt tests pass
    GitHub->>Build: workflow_call -> build-and-publish
    Build->>Build: cache deps, go mod download + go mod verify, build artifacts
    Build->>Build: verify artifacts, gen SHA256, upload release assets (VT scan if key)
    Build-->>GitHub: release artifacts published
  else tests fail
    Test-->>GitHub: fail status, stop build
  end
  GitHub->>GitHub: Security Scan (scheduled/PR) runs CodeQL, govulncheck, staticcheck
  GitHub->>GitHub: Dependency Review runs + license report uploaded
Loading
sequenceDiagram
  autonumber
  participant App as "App (main)"
  participant Ev as "startEventHandlersWithContext"
  participant Menu as "handleMenuItemClicksWithContext"
  participant Upd as "checkForUpdateWithContext / startUpdateClickHandlerWithContext"
  Developer->>App: start()
  App->>Ev: create context.Context
  Ev->>Menu: spawn menu handler goroutine with ctx
  Ev->>Upd: spawn update checker/handler goroutines with ctx
  Note right of Menu: menu loop listens for item clicks and ctx.Done()
  Note right of Upd: update loop polls/handles clicks and ctx.Done()
  Developer->>App: cancel context / shutdown
  App->>Menu: ctx.Done() -> exit loop
  App->>Upd: ctx.Done() -> exit loop
  Menu-->>App: cleanup
  Upd-->>App: cleanup
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Refactor/2025/09 #12 — Strong overlap: refactors App/menu/handler codepaths and may conflict with the context-driven handler changes and added non-Windows stubs.

Poem

I hop through pipelines, badges gleam anew,
Stubs for every platform, tests that poke and chew.
Checksums clack and scanners peek, coverage numbers grow,
Contexts wrap the handlers so graceful shutdowns flow.
I nibble at the CI carrots and watch the green checks glow. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "add tests" accurately reflects the primary focus of the changeset, which adds a large suite of unit/integration tests, test stubs, and related test infrastructure; it is concise and directly related to the main change. While minimal, it is clear enough for a teammate scanning PR history to understand the primary intent.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/add-unit-tests

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e854be9 and 0ed1900.

📒 Files selected for processing (1)
  • .github/workflows/test.yml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/test.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test on Windows
  • GitHub Check: CodeQL Analysis

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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.

@github-actions

github-actions Bot commented Sep 19, 2025 •

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

Scorecard details
PackageVersionScoreDetails
actions/actions/checkout 4.*.* 🟢 6.8
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Maintained🟢 57 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 5
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Packaging⚠️ -1packaging workflow not detected
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ -1internal error: error during GetBranch(releases/v2): error during branchesHandler.query: internal error: githubv4.Query: Resource not accessible by integration
SAST🟢 9SAST tool detected but not run on all commits
Vulnerabilities🟢 100 existing vulnerabilities detected
actions/actions/dependency-review-action 4.*.* 🟢 7.9
Details
CheckScoreReason
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Code-Review🟢 10all changesets reviewed
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Binary-Artifacts🟢 10no binaries found in the repo
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy🟢 9security policy file detected
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Pinned-Dependencies⚠️ 2dependency not pinned by hash detected -- score normalized to 2
Signed-Releases⚠️ -1no releases found
Branch-Protection🟢 6branch protection is not maximal on development and all release branches
SAST🟢 10SAST tool is run on all commits
Vulnerabilities🟢 91 existing vulnerabilities detected
actions/actions/setup-go 5.*.* 🟢 6.1
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 68 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 6
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 9SAST tool is not run on all commits -- score normalized to 9
Vulnerabilities🟢 100 existing vulnerabilities detected
actions/actions/upload-artifact 4.*.* 🟢 4.8
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained⚠️ 12 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 9SAST tool detected but not run on all commits
Vulnerabilities⚠️ 19 existing vulnerabilities detected
actions/actions/checkout 4.*.* 🟢 6.8
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Maintained🟢 57 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 5
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Packaging⚠️ -1packaging workflow not detected
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ -1internal error: error during GetBranch(releases/v2): error during branchesHandler.query: internal error: githubv4.Query: Resource not accessible by integration
SAST🟢 9SAST tool detected but not run on all commits
Vulnerabilities🟢 100 existing vulnerabilities detected
actions/actions/setup-go 5.*.* 🟢 6.1
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 68 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 6
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 9SAST tool is not run on all commits -- score normalized to 9
Vulnerabilities🟢 100 existing vulnerabilities detected
actions/github/codeql-action/analyze 3.*.* UnknownUnknown
actions/github/codeql-action/autobuild 3.*.* UnknownUnknown
actions/github/codeql-action/init 3.*.* UnknownUnknown
actions/github/codeql-action/upload-sarif 3.*.* UnknownUnknown
actions/actions/cache 4.*.* 🟢 7.4
Details
CheckScoreReason
Maintained🟢 1014 commit(s) and 4 issue activity found in the last 90 days -- score normalized to 10
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ -1internal error: error during GetBranch(releases/v1): error during branchesHandler.query: internal error: githubv4.Query: Resource not accessible by integration
SAST🟢 10SAST tool is run on all commits
Vulnerabilities🟢 100 existing vulnerabilities detected
actions/actions/checkout 4.*.* 🟢 6.8
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Maintained🟢 57 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 5
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Packaging⚠️ -1packaging workflow not detected
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ -1internal error: error during GetBranch(releases/v2): error during branchesHandler.query: internal error: githubv4.Query: Resource not accessible by integration
SAST🟢 9SAST tool detected but not run on all commits
Vulnerabilities🟢 100 existing vulnerabilities detected
actions/actions/setup-go 5.*.* 🟢 6.1
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 68 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 6
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 9SAST tool is not run on all commits -- score normalized to 9
Vulnerabilities🟢 100 existing vulnerabilities detected
actions/actions/upload-artifact 4.*.* 🟢 4.8
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained⚠️ 12 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 9SAST tool detected but not run on all commits
Vulnerabilities⚠️ 19 existing vulnerabilities detected
actions/codecov/codecov-action 4.*.* 🟢 7.8
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 1013 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Dependency-Update-Tool🟢 10update tool detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Packaging⚠️ -1packaging workflow not detected
Pinned-Dependencies⚠️ 2dependency not pinned by hash detected -- score normalized to 2
License🟢 10license file detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Vulnerabilities🟢 100 existing vulnerabilities detected
Security-Policy🟢 10security policy file detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration
Signed-Releases⚠️ -1no releases found
Fuzzing⚠️ 0project is not fuzzed
SAST🟢 10SAST tool is run on all commits
CI-Tests🟢 1030 out of 30 merged PRs checked by a CI test -- score normalized to 10
Contributors🟢 10project has 13 contributing companies or organizations
actions/golangci/golangci-lint-action 6.*.* 🟢 6.4
Details
CheckScoreReason
Code-Review🟢 5Found 2/4 approved changesets -- score normalized to 5
Maintained🟢 1020 commit(s) and 7 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Signed-Releases⚠️ -1no releases found
Branch-Protection🟢 4branch protection is not maximal on development and all release branches
Security-Policy🟢 10security policy file detected
SAST🟢 10SAST tool is run on all commits
Vulnerabilities🟢 91 existing vulnerabilities detected

Scanned Files

  • .github/workflows/dependency-review.yml
  • .github/workflows/security.yml
  • .github/workflows/test.yml

@github-advanced-security

Copy link
Copy Markdown

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (9)
.github/workflows/test.yml (1)

162-165: Remove deprecated action inputs.

The skip-pkg-cache and skip-build-cache inputs are not supported in golangci-lint-action@v6. v5.0.0+ removes skip-pkg-cache and skip-build-cache because the cache related to Go itself is already handled by actions/setup-go.

Apply this diff to use the correct inputs:

-        skip-cache: false
-        skip-pkg-cache: false
-        skip-build-cache: false
+        skip-cache: false
+        skip-save-cache: false
utils_test.go (1)

25-41: Improve cross-platform test robustness.

The test skips Windows but doesn't verify the intended behavior. Consider adding assertions to check that openBrowser handles non-Windows platforms appropriately (e.g., by checking if an error is logged or a specific behavior occurs).

updater_logic_test.go (1)

64-73: Improve test assertion logic.

The test logic strings.Contains("winget install "+expectedPackageID, expectedPackageID) will always return true since the expected package ID is explicitly included in the constructed string. Consider testing actual command generation or refactor to validate the real update command components.

Apply this diff to improve the test:

-    // This tests the knowledge that the update uses winget
-    if !strings.Contains("winget install "+expectedPackageID, expectedPackageID) {
-        t.Error("Update command should reference correct package ID")
-    }
+    // Test the actual command construction logic if available
+    // or verify that the package ID constant is correct
+    if expectedPackageID != "5mdt.WinGoDarkTray" {
+        t.Errorf("Package ID should be '5mdt.WinGoDarkTray', got %s", expectedPackageID)
+    }
pure_funcs_test.go (1)

172-208: Consider using constants for expected UI text values.

The hardcoded UI text mappings in the test could become out of sync with the actual application constants. Consider importing and testing against the actual constants from the main codebase to ensure consistency.

ui_texts_test.go (5)

69-80: Make content checks case‑insensitive to reduce brittleness.

Current assertions will fail if copy changes only differ by case (e.g., “windows” vs “Windows”). Use case‑insensitive containment.

Apply this diff:

- if !strings.Contains(menuTitles.ToggleWinToDark, "Windows") {
+ if !strings.Contains(strings.ToLower(menuTitles.ToggleWinToDark), "windows") {
   t.Error("ToggleWinToDark should contain 'Windows'")
 }
- if !strings.Contains(menuTitles.ToggleWinToLight, "Windows") {
+ if !strings.Contains(strings.ToLower(menuTitles.ToggleWinToLight), "windows") {
   t.Error("ToggleWinToLight should contain 'Windows'")
 }
- if !strings.Contains(menuTitles.ToggleBothToDark, "both") {
+ if !strings.Contains(strings.ToLower(menuTitles.ToggleBothToDark), "both") {
   t.Error("ToggleBothToDark should contain 'both'")
 }
- if !strings.Contains(menuTitles.ToggleBothToLight, "both") {
+ if !strings.Contains(strings.ToLower(menuTitles.ToggleBothToLight), "both") {
   t.Error("ToggleBothToLight should contain 'both'")
 }
- if !strings.Contains(tooltips.Default, "windows") || !strings.Contains(tooltips.Default, "toggle") {
+ if !strings.Contains(strings.ToLower(tooltips.Default), "windows") || !strings.Contains(strings.ToLower(tooltips.Default), "toggle") {
   t.Error("Default tooltip should describe the app's purpose")
 }

Also applies to: 93-96


8-46: Reduce duplication with a table‑driven structure test.

This keeps the file concise and improves failure diagnostics; also trims accidental whitespace.

-func TestMenuTitlesStructure(t *testing.T) {
-	// Test that all menu titles are non-empty
-	if menuTitles.AppName == "" {
-		t.Error("AppName should not be empty")
-	}
-	if menuTitles.EnableAutorun == "" {
-		t.Error("EnableAutorun should not be empty")
-	}
-	if menuTitles.Quit == "" {
-		t.Error("Quit should not be empty")
-	}
-	if menuTitles.EnableAutorunChecked == "" {
-		t.Error("EnableAutorunChecked should not be empty")
-	}
-	if menuTitles.EnableAutorunUnchecked == "" {
-		t.Error("EnableAutorunUnchecked should not be empty")
-	}
-	if menuTitles.ToggleAppToDark == "" {
-		t.Error("ToggleAppToDark should not be empty")
-	}
-	if menuTitles.ToggleAppToLight == "" {
-		t.Error("ToggleAppToLight should not be empty")
-	}
-	if menuTitles.ToggleWinToDark == "" {
-		t.Error("ToggleWinToDark should not be empty")
-	}
-	if menuTitles.ToggleWinToLight == "" {
-		t.Error("ToggleWinToLight should not be empty")
-	}
-	if menuTitles.ToggleBothToDark == "" {
-		t.Error("ToggleBothToDark should not be empty")
-	}
-	if menuTitles.ToggleBothToLight == "" {
-		t.Error("ToggleBothToLight should not be empty")
-	}
-	if menuTitles.UpdateNow == "" {
-		t.Error("UpdateNow should not be empty")
-	}
-}
+func TestMenuTitlesStructure(t *testing.T) {
+	t.Parallel()
+	cases := []struct {
+		name string
+		val  string
+	}{
+		{"AppName", menuTitles.AppName},
+		{"EnableAutorun", menuTitles.EnableAutorun},
+		{"Quit", menuTitles.Quit},
+		{"EnableAutorunChecked", menuTitles.EnableAutorunChecked},
+		{"EnableAutorunUnchecked", menuTitles.EnableAutorunUnchecked},
+		{"ToggleAppToDark", menuTitles.ToggleAppToDark},
+		{"ToggleAppToLight", menuTitles.ToggleAppToLight},
+		{"ToggleWinToDark", menuTitles.ToggleWinToDark},
+		{"ToggleWinToLight", menuTitles.ToggleWinToLight},
+		{"ToggleBothToDark", menuTitles.ToggleBothToDark},
+		{"ToggleBothToLight", menuTitles.ToggleBothToLight},
+		{"UpdateNow", menuTitles.UpdateNow},
+	}
+	for _, c := range cases {
+		if strings.TrimSpace(c.val) == "" {
+			t.Errorf("%s should not be empty", c.name)
+		}
+	}
+}

48-48: Parallelize independent tests.

These tests only read constants/globals; safe to run in parallel for faster CI.

 func TestMenuTitlesContent(t *testing.T) {
+	t.Parallel()

83-83: Parallelize independent tests.

 func TestTooltipsStructure(t *testing.T) {
+	t.Parallel()

98-98: Parallelize independent tests.

 func TestNotificationTextsStructure(t *testing.T) {
+	t.Parallel()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fdfc4c and 41161a5.

📒 Files selected for processing (18)
  • .codecov.yml (1 hunks)
  • .github/dependabot.yml (1 hunks)
  • .github/workflows/build-and-publish.yml (4 hunks)
  • .github/workflows/dependency-review.yml (1 hunks)
  • .github/workflows/security.yml (1 hunks)
  • .github/workflows/test.yml (1 hunks)
  • .gitignore (1 hunks)
  • .golangci.yml (1 hunks)
  • .pre-commit-config.yaml (0 hunks)
  • README.md (1 hunks)
  • app_test.go (1 hunks)
  • cross_platform_test.go (1 hunks)
  • pure_funcs_test.go (1 hunks)
  • semver_utils_test.go (1 hunks)
  • testable_funcs.go (1 hunks)
  • ui_texts_test.go (1 hunks)
  • updater_logic_test.go (1 hunks)
  • utils_test.go (1 hunks)
💤 Files with no reviewable changes (1)
  • .pre-commit-config.yaml
🧰 Additional context used
🧬 Code graph analysis (1)
app_test.go (1)
main.go (1)
  • NewApp (19-26)
🪛 actionlint (1.7.7)
.github/workflows/test.yml

163-163: input "skip-pkg-cache" is not defined in action "golangci/golangci-lint-action@v6". available inputs are "args", "cache-invalidation-interval", "github-token", "install-mode", "only-new-issues", "problem-matchers", "skip-cache", "skip-save-cache", "version", "working-directory"

(action)


164-164: input "skip-build-cache" is not defined in action "golangci/golangci-lint-action@v6". available inputs are "args", "cache-invalidation-interval", "github-token", "install-mode", "only-new-issues", "problem-matchers", "skip-cache", "skip-save-cache", "version", "working-directory"

(action)

.github/workflows/build-and-publish.yml

90-90: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)

🪛 GitHub Check: Go Vulnerability Check
testable_funcs.go

[failure] 28-28:
setTemporaryTooltip redeclared in this block


[failure] 24-24:
logEvent redeclared in this block


[failure] 20-20:
showError redeclared in this block

🔇 Additional comments (18)
README.md (1)

4-9: LGTM! Good addition of status badges.

The badges provide valuable visibility into the project's CI/CD status, test coverage, and code quality. They align well with the new workflows and tooling introduced in this PR.

.gitignore (1)

1-8: LGTM! Appropriate gitignore strategy for development tooling.

The global dotfile ignore with specific exceptions is a clean approach that prevents accidentally tracking IDE configuration files while ensuring essential development tooling (CI/CD, linting, coverage) is version controlled.

.codecov.yml (1)

1-44: LGTM! Comprehensive Codecov configuration with appropriate settings.

The configuration establishes reasonable coverage thresholds (70%), proper CI integration, and logical ignore patterns. The Windows-specific flag and path configuration aligns well with the project's platform focus.

.github/dependabot.yml (1)

8-24: LGTM! Well-configured dependency management.

The weekly schedule, appropriate reviewers, and reasonable PR limits (5 for Go, 3 for GitHub Actions) provide good automation without overwhelming maintainers.

semver_utils_test.go (2)

8-55: LGTM! Comprehensive test coverage for version utilities.

The table-driven tests cover all edge cases including padding scenarios, empty inputs, and boundary conditions. The test structure follows Go testing best practices.


57-104: LGTM! Thorough testing of the max function.

Good coverage of edge cases including negative numbers and equality scenarios. The tests are well-structured and follow consistent patterns.

testable_funcs.go (1)

49-56: LGTM! Appropriate testing factory function.

The NewAppForTesting function provides a clean way to create mock app instances for tests with reasonable defaults. The version defaulting to "v0.0.0" when empty is appropriate for testing scenarios.

.golangci.yml (2)

24-41: LGTM! Comprehensive linter configuration with appropriate exceptions.

The enabled linters provide good coverage of common Go issues. The disabled linters make sense - gosec for Windows registry operations and maligned for struct alignment being non-critical.


47-66: LGTM! Well-thought exclusion rules for different contexts.

The exclusions are appropriate:

  • Test files should have relaxed linting rules
  • Windows-specific code exclusions from cross-platform import checks
  • Long lines allowed in tests for readability

These rules support the cross-platform testing approach introduced in this PR.

cross_platform_test.go (2)

12-45: LGTM! Comprehensive version comparison testing.

The test cases cover edge cases including mixed version formats, invalid versions, and various comparison scenarios. The cross-platform build constraints ensure these tests run on non-Windows systems where the actual Windows APIs aren't available.


47-69: Duplicate tests — consolidate or justify.

TestMaxCrossPlatform in cross_platform_test.go (lines 47–69) is identical to TestMax in semver_utils_test.go (lines 57–104). Keep a single test or add explicit build tags / different intent to justify both.

.github/workflows/dependency-review.yml (1)

50-50: LGTM: Well-configured dependency and license scanning.

This workflow provides comprehensive dependency and license review with appropriate configurations. The use of the || true flag in the license report generation is wise to prevent failures when no licenses are detected.

.github/workflows/build-and-publish.yml (1)

30-30: Verify Go version compatibility.

Go version '1.25' is specified, but this version may not exist yet. Please verify the available Go versions and consider using a stable version like '1.23'.

.github/workflows/security.yml (1)

1-103: LGTM: Comprehensive security scanning setup.

This workflow provides excellent security coverage with multiple scanning tools (govulncheck, CodeQL, Nancy, and Gosec). The configuration includes proper permissions, scheduled runs, and SARIF uploads for integration with GitHub Security features.

app_test.go (1)

30-55: LGTM: Thorough App initialization testing.

The test covers various version scenarios and validates proper initialization of menu items. The table-driven approach is well-structured and comprehensive.

pure_funcs_test.go (1)

12-81: LGTM: Well-designed pure function tests.

The version comparison logic is thoroughly tested with edge cases, and the implementation handles version padding correctly. The test coverage includes both valid and invalid inputs.

ui_texts_test.go (2)

48-81: Overall: good coverage and intent checks.

Nice mix of structure and content assertions; naming is clear and failures will be actionable.

Also applies to: 98-117


1-1: Resolve — non-Windows stubs present; no Windows-only build tag required

ui_texts.go defines menuTitles, tooltips, and notificationTexts (see ui_texts.go:3,31,39), so ui_texts_test.go will compile cross-platform.

Comment thread .github/workflows/build-and-publish.yml Outdated
Comment thread testable_funcs.go Outdated
Comment on lines +20 to +34
func showError(message string) {
fmt.Printf("Error: %s\n", message)
}

func logEvent(level interface{}, message string) {
fmt.Printf("Log: %s\n", message)
}

func setTemporaryTooltip(message string, duration interface{}) {
fmt.Printf("Tooltip: %s\n", message)
}

func openRegistryKey(path string, access uint32) (interface{}, error) {
return mockRegistryKey{}, errors.New("registry not available on non-Windows")
}

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

🧩 Analysis chain

Fix function redeclaration errors.

The static analysis correctly identifies that these functions are being redeclared. These stub implementations conflict with the actual Windows implementations when both are compiled together.

Based on the search results, the issue is clear. No identifier may be declared twice in the same block, and no identifier may be declared in both the file and package block. The build tags are working correctly - the problem is that these functions already exist in the main Windows implementation files, and static analysis tools are seeing both versions simultaneously.

Let me verify if these functions exist in other files:


🏁 Script executed:

#!/bin/bash
# Description: Check if these stub functions are already implemented in Windows-specific files

echo "Searching for Windows-specific function implementations..."
rg -n --type=go -C3 "func showError|func logEvent|func setTemporaryTooltip|func openRegistryKey" -g '!testable_funcs.go'

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo
echo "---- show testable_funcs.go (first 120 lines) ----"
if [ -f testable_funcs.go ]; then
  sed -n '1,120p' testable_funcs.go || true
else
  echo "testable_funcs.go not found at repo root"
fi
echo
echo "---- list tracked .go files (first 200) ----"
git ls-files '*.go' | sed -n '1,200p' || true
echo
echo "---- search for function DEFINITIONS in other .go files (exclude testable_funcs.go) ----"
OTHER_FILES=$(git ls-files '*.go' | grep -v '^testable_funcs.go$' || true)
if [ -z "$OTHER_FILES" ]; then
  echo "No other tracked .go files found"
else
  echo "$OTHER_FILES" | sed -n '1,200p'
  rg -n --hidden --no-ignore -S "func\s+(showError|logEvent|setTemporaryTooltip|openRegistryKey)\s*\(" $(echo $OTHER_FILES) -C3 || true
fi
echo
echo "---- search in windows-specific files (*_windows.go) for DEFINITIONS ----"
rg -n --hidden --no-ignore -S "func\s+(showError|logEvent|setTemporaryTooltip|openRegistryKey)\s*\(" -g '*_windows.go' -C3 || true
echo
echo "---- fallback: search entire repo for any occurrences of these identifiers ----"
rg -n --hidden --no-ignore -S "(showError|logEvent|setTemporaryTooltip|openRegistryKey)" -C3 || true

Length of output: 13455


Fix function redeclaration errors.

  • testable_funcs.go (//go:build !windows) provides stubs: showError, logEvent, setTemporaryTooltip, openRegistryKey.
  • Duplicates found in: logging.go (logEvent ≈ line 71, showError ≈ line 111, setTemporaryTooltip ≈ line 124) and utils.go (openRegistryKey ≈ line 23).
  • Action: ensure only one implementation is compiled per GOOS — e.g. mark Windows-only files with //go:build windows or move Windows-specific functions into *_windows.go, or remove/rename the non-Windows stubs so the package no longer redeclares these identifiers.
🧰 Tools
🪛 GitHub Check: Go Vulnerability Check

[failure] 28-28:
setTemporaryTooltip redeclared in this block


[failure] 24-24:
logEvent redeclared in this block


[failure] 20-20:
showError redeclared in this block

🤖 Prompt for AI Agents
In testable_funcs.go (lines 20-34) you have non-Windows stubs that conflict with
Windows implementations in logging.go and utils.go; to resolve redeclaration
errors, ensure only one implementation is built per GOOS: move or rename the
Windows-specific functions into files with a //go:build windows (or
*_windows.go) tag (or add that build tag to the existing Windows files), or
conversely remove/rename the non-Windows stubs in testable_funcs.go so the
identifiers are not duplicated; pick one approach and make the Windows-only
implementations live in windows-tagged files while keeping the non-Windows stubs
in files with //go:build !windows.

use 1.25 version of golang

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tray_handlers.go (1)

10-25: Stop the goroutine after Quit to avoid spinning on closed channels.
When systray.Quit() is invoked, ClickedCh may close; a select on a closed channel can hot-loop. Return immediately.

Apply:

       case <-quitItem.ClickedCh:
-        systray.Quit()
+        systray.Quit()
+        return
♻️ Duplicate comments (1)
.github/workflows/build-and-publish.yml (1)

89-98: Fix secrets context usage in if condition (repeat).

secrets is not allowed in step-level if. Export to env and check env.VT_API_KEY.

-    - name: VirusTotal Scan
-      if: ${{ secrets.VT_API_KEY != '' }}
+    - name: VirusTotal Scan
+      if: env.VT_API_KEY != ''
       uses: crazy-max/ghaction-virustotal@v4
+      env:
+        VT_API_KEY: ${{ secrets.VT_API_KEY }}
       with:
-        vt_api_key: ${{ secrets.VT_API_KEY }}
+        vt_api_key: ${{ env.VT_API_KEY }}
         files: |
           ./build/WinGoDarkTray-x32.exe
           ./build/WinGoDarkTray-x64.exe
           ./build/WinGoDarkTray-arm64.exe
🧹 Nitpick comments (29)
README.md (1)

78-97: Remove duplicate "Building from Source" section.

There are two identical "Building from Source" sections (lines 58-77 and 78-97). The duplicate should be removed to avoid confusion.

Apply this diff to remove the duplicate section:

-### Building from Source
-
-To build WinGoDarkTray from source, follow these steps:
-
-1. Make sure Go 1.24+ is installed. You can download Go from [here](https://golang.org/dl/).
-2. Clone this repository:
-
-   ```bash
-   git clone https://github.com/5mdt/WinGoDarkTray.git
-   cd WinGoDarkTray
-   ```
-
-3. Build the application:
-
-   ```bash
-   go build -o WinGoDarkTray.exe
-   ```
-
-4. Double-click `WinGoDarkTray.exe` to launch it, and it will appear in your system tray.
-5. (Optional) Press tray icon and enable autorun.
autorun.go (1)

1-5: Consider modernizing build constraints syntax.

The go command now understands //go:build lines and prefers them over // +build lines since Go 1.17. The new syntax uses boolean expressions, just like Go, and should be less error-prone. While both syntaxes work, the newer //go:build syntax is preferred and more explicit.

Consider using only the modern syntax for consistency across the codebase:

 // autorun.go
 
 //go:build windows
-// +build windows

Apply this pattern to all files with Windows build constraints in this PR (autorun.go, logging.go, updater.go, etc.).

logging.go (1)

1-5: Consider modernizing build constraints syntax.

Same as autorun.go - consider using only the modern //go:build windows syntax without the legacy // +build windows line for better maintainability and alignment with current Go best practices.

 // logging.go
 
 //go:build windows
-// +build windows
updater.go (2)

1-5: Consider modernizing build constraints syntax.

Same as other Windows-gated files - consider using only the modern //go:build windows syntax for consistency.

 // updater.go
 
 //go:build windows
-// +build windows

81-81: Simplify the update command construction.

The current command construction is overly complex with nested cmd commands. Consider breaking this into parts for better readability and maintainability.

 func runUpdateCommand() {
-	cmd := exec.Command("cmd", "/C", "start", "cmd", "/K", "echo Trying to update WinGoDarkTray, using winget app && winget install 5mdt.WinGoDarkTray && echo If nothing installed, please try again later. Winget repository moderators must approve new package first.")
+	updateScript := "echo Trying to update WinGoDarkTray, using winget app && winget install 5mdt.WinGoDarkTray && echo If nothing installed, please try again later. Winget repository moderators must approve new package first."
+	cmd := exec.Command("cmd", "/C", "start", "cmd", "/K", updateScript)
 	err := cmd.Start()
 	if err != nil {
 		showError("Failed to run update command: " + err.Error())
 	}
 }
theme.go (2)

42-57: Reduce duplication and make mode intent explicit.
Minor readability: extract a small helper and name the DWORDs.

Apply:

+const (
+  modeDark  uint32 = 0
+  modeLight uint32 = 1
+)
+
 func setBothThemeModes(lightMode bool) error {
-  var newMode uint32
-  if lightMode {
-    newMode = 1
-  } else {
-    newMode = 0
-  }
+  newMode := modeDark
+  if lightMode {
+    newMode = modeLight
+  }

116-152: Title updates look correct; guard against nil menu items if called earlier.
Currently called after creation, but if this gets reused elsewhere, a nil guard would prevent panics.

Apply:

 func (a *App) updateThemeToggleTitles() {
+  if a.toggleAppItem == nil || a.toggleSystemItem == nil || a.toggleWindowsItem == nil {
+    return
+  }
tray_handlers.go (1)

10-25: Avoid duplicate Quit handling across goroutines.
You’re calling systray.Quit() here and also in startEventHandlers. Prefer a single place and signal via a quit channel.

Proposed direction:

  • Remove the Quit case here and handle Quit only in startEventHandlers, or
  • Pass a quitCh into handleMenuItemClicks and select on it to return.
utils.go (1)

16-26: Windows guard is redundant inside a Windows-gated file; validate scheme instead.
Since this file only builds on Windows, the GOOS check can be dropped. Also, consider allowing only http/https to avoid odd schemes.

Apply:

 func openBrowser(urlStr string) {
-  if runtime.GOOS != "windows" {
-    showError("Opening URLs is only supported on Windows.")
-    return
-  }
-
-  err := exec.Command("rundll32", "url.dll,FileProtocolHandler", urlStr).Start()
+  // optional: basic scheme guard
+  if !strings.HasPrefix(strings.ToLower(urlStr), "http://") && !strings.HasPrefix(strings.ToLower(urlStr), "https://") {
+    showError("Only http(s) URLs are supported.")
+    return
+  }
+  err := exec.Command("rundll32", "url.dll,FileProtocolHandler", urlStr).Start()
   if err != nil {
     showError("Failed to open browser: " + err.Error())
   }
 }

And add:

 import (
   "os"
   "os/exec"
-  "runtime"
+  "strings"
main.go (1)

97-108: Duplicate Quit handling and goroutine lifecycle.
You both listen for quitItem.ClickedCh here and in handleMenuItemClicks. Consolidate and ensure all goroutines exit when quitting.

Option:

  • Handle Quit only here; pass a quitCh into handleMenuItemClicks and return there on <-quitCh.
app_test.go (1)

32-57: Optional: run subtests in parallel.
Safe here and speeds up CI a bit.

Apply:

 for _, tt := range tests {
   t.Run(tt.name, func(t *testing.T) {
+    t.Parallel()
.golangci.yml (3)

43-46: Drop maligned; use fieldalignment (vet) if needed.

maligned is archived. If you want struct alignment checks, prefer govet’s fieldalignment (already covered indirectly).

   disable:
-    - gosec  # Windows registry operations trigger security warnings
-    - maligned  # Struct alignment not critical for this app
+    - gosec  # Windows registry operations trigger security warnings

5-7: Revisit build-tags: windows; it doesn’t switch GOOS.

On Ubuntu runners, this won’t include //go:build windows files. Either run golangci-lint on windows-latest or drop this to lint the Linux build.

Would you like me to add a parallel lint job on windows-latest?


49-56: Clean up exclude rules for linters not enabled.

You exclude dupl and lll in tests, but they’re not enabled. Either enable them or remove these excludes to reduce config noise.

   exclude-rules:
     # Exclude some linters from running on tests files
     - path: _test\.go
       linters:
         - gocyclo
         - errcheck
-        - dupl
-        - gosec
+        - gosec
@@
     # Allow long lines in test files
     - path: _test\.go
       linters:
-        - lll
+        # - lll

Also applies to: 63-67

updater_logic_test.go (2)

46-64: Stronger URL validation via parsing vs substring checks.

Use url.Parse to validate scheme/host/path deterministically.

 import (
 	"strings"
 	"testing"
+	"net/url"
 )
@@
 func TestGithubAPIURL(t *testing.T) {
 	expectedURL := "https://api.github.com/repos/5mdt/WinGoDarkTray/releases/latest"
 	if githubReleasesAPI != expectedURL {
 		t.Errorf("githubReleasesAPI = %q, want %q", githubReleasesAPI, expectedURL)
 	}
 
-	// Verify URL is well-formed
-	if !strings.HasPrefix(githubReleasesAPI, "https://") {
-		t.Error("githubReleasesAPI should use HTTPS")
-	}
-
-	if !strings.Contains(githubReleasesAPI, "github.com") {
-		t.Error("githubReleasesAPI should point to GitHub")
-	}
-
-	if !strings.Contains(githubReleasesAPI, "5mdt/WinGoDarkTray") {
-		t.Error("githubReleasesAPI should point to correct repository")
-	}
+	u, err := url.Parse(githubReleasesAPI)
+	if err != nil {
+		t.Fatalf("invalid URL: %v", err)
+	}
+	if u.Scheme != "https" {
+		t.Error("githubReleasesAPI should use HTTPS")
+	}
+	if u.Host != "api.github.com" || !strings.HasSuffix(u.Path, "/repos/5mdt/WinGoDarkTray/releases/latest") {
+		t.Error("githubReleasesAPI should point to the correct GitHub API path")
+	}
 }

66-75: Test is vacuous; it asserts a literal contains its own substring.

Replace with a test of real code (e.g., function/const that builds the winget command) or remove.

If you expose a helper like buildWingetInstallCmd(packageID), I can wire a meaningful test.

.github/workflows/security.yml (1)

76-80: Pin Nancy action to a tag or SHA; avoid @main.

Using @main risks breaking on upstream changes. Pin to a stable tag or commit SHA.

-      uses: sonatypecommunity/nancy-github-action@main
+      uses: sonatypecommunity/nancy-github-action@v1
ui_texts_test.go (1)

5-8: Make content checks case-insensitive to avoid fragile failures.

UI strings may vary in case (“Windows”, “Both”, etc.). Normalize during tests.

 import (
 	"strings"
 	"testing"
 )
 
+func containsCI(s, sub string) bool { return strings.Contains(strings.ToLower(s), strings.ToLower(sub)) }
@@
-	if !strings.Contains(menuTitles.AppName, "WinGoDarkTray") {
+	if !containsCI(menuTitles.AppName, "WinGoDarkTray") {
 		t.Error("AppName should contain 'WinGoDarkTray'")
 	}
@@
-	if !strings.Contains(menuTitles.ToggleAppToDark, "Dark") {
+	if !containsCI(menuTitles.ToggleAppToDark, "Dark") {
 		t.Error("ToggleAppToDark should contain 'Dark'")
 	}
-	if !strings.Contains(menuTitles.ToggleAppToLight, "Light") {
+	if !containsCI(menuTitles.ToggleAppToLight, "Light") {
 		t.Error("ToggleAppToLight should contain 'Light'")
 	}
-	if !strings.Contains(menuTitles.ToggleWinToDark, "Windows") {
+	if !containsCI(menuTitles.ToggleWinToDark, "Windows") {
 		t.Error("ToggleWinToDark should contain 'Windows'")
 	}
-	if !strings.Contains(menuTitles.ToggleWinToLight, "Windows") {
+	if !containsCI(menuTitles.ToggleWinToLight, "Windows") {
 		t.Error("ToggleWinToLight should contain 'Windows'")
 	}
-	if !strings.Contains(menuTitles.ToggleBothToDark, "both") {
+	if !containsCI(menuTitles.ToggleBothToDark, "both") {
 		t.Error("ToggleBothToDark should contain 'both'")
 	}
-	if !strings.Contains(menuTitles.ToggleBothToLight, "both") {
+	if !containsCI(menuTitles.ToggleBothToLight, "both") {
 		t.Error("ToggleBothToLight should contain 'both'")
 	}
@@
-	if !strings.Contains(tooltips.Default, "windows") || !strings.Contains(tooltips.Default, "toggle") {
+	if !containsCI(tooltips.Default, "windows") || !containsCI(tooltips.Default, "toggle") {
 		t.Error("Default tooltip should describe the app's purpose")
 	}
@@
-	if !strings.Contains(notificationTexts.Error, "WinGoDarkTray") {
+	if !containsCI(notificationTexts.Error, "WinGoDarkTray") {
 		t.Error("Error notification should contain app name")
 	}
-	if !strings.Contains(notificationTexts.UpdateAvailableTitle, "Update") {
+	if !containsCI(notificationTexts.UpdateAvailableTitle, "Update") {
 		t.Error("UpdateAvailableTitle should contain 'Update'")
 	}

Also applies to: 50-83, 94-98, 112-119

.github/workflows/build-and-publish.yml (1)

59-75: Artifact names: allow x86/386 variant to avoid false negatives.

If build.sh outputs x86/386 instead of x32, the step will fail.

-        if [ ! -f "./build/WinGoDarkTray-x32.exe" ]; then
-          echo "Error: x32 executable not found"
-          exit 1
-        fi
+        if [ ! -f "./build/WinGoDarkTray-x32.exe" ] && [ ! -f "./build/WinGoDarkTray-x86.exe" ] && [ ! -f "./build/WinGoDarkTray-386.exe" ]; then
+          echo "Error: 32-bit executable not found (expected one of x32/x86/386)"
+          exit 1
+        fi
.github/workflows/dependency-review.yml (1)

47-52: Drop missing template flag from go-licenses command

There’s no licenses.tpl in the repo, so go-licenses report --template licenses.tpl errors (silently via || true) and yields an empty report. Remove the flag:

-        go-licenses report ./... --template licenses.tpl > licenses-report.txt || true
+        go-licenses report ./... > licenses-report.txt || true

If you need custom formatting, add a licenses.tpl file instead.

CLAUDE.md (1)

26-26: Fix Markdown: remove literal "\n\n" and split CI/CD section into its own heading.

Current line renders incorrectly; split into separate lines/headings.

Apply this diff:

-**Note**: Full test suite requires Windows environment due to Windows API dependencies. See `TESTING.md` for comprehensive testing strategy.\n\n### CI/CD Workflows\nThe project includes comprehensive GitHub Actions workflows:
+**Note**: Full test suite requires Windows environment due to Windows API dependencies. See TESTING.md for comprehensive testing strategy.
+
+### CI/CD Workflows
+The project includes comprehensive GitHub Actions workflows:
semver_test.go (1)

7-30: Optional: add a couple more edge cases (leading zeros/whitespace).

Strengthens parsing expectations without changing behavior.

Apply this diff to extend the table:

 		{"v1.2.3", "v1.10.0", false},
 		{"v1.2.4", "v1.2.3", true},
 		{"v2.0.0", "v1.9.9", true},
+		{"01.02.003", "1.2.3", false},
+		{"1.2.3 ", "1.2.3", false},
semver_utils_test.go (1)

59-106: Minor: consider t.Parallel in subtests to speed up.

Safe here; helpers are pure.

Apply this diff:

 func TestMax(t *testing.T) {
 	tests := []struct {
@@
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			got := max(tt.a, tt.b)
 			if got != tt.want {
 				t.Errorf("max(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
 			}
 		})
 	}
 }
cross_platform_test.go (1)

14-71: Avoid duplication with semver_test.go by sharing test vectors.

Extract common version/max test cases into a helper to keep one source of truth.

Example pattern:

-// local tests slice here
+// in a shared *_test.go file:
+var versionCompareTests = []struct{ latest, current string; want bool }{
+  {"0.9.9","0.9.8",true}, /* ... */
+}

Then both tests iterate over versionCompareTests.

pure_funcs_test.go (5)

1-11: Prune imports after removing local helpers/UI block.

Keep only what’s needed (likely just testing; maybe reflect).


54-73: Optional: dedupe version test cases with semver_test.go.

Extract shared cases to avoid drift.


75-83: Optional: t.Parallel inside subtests for speed.

Safe for pure comparisons.

Apply:

-		t.Run(tt.latest+" vs "+tt.current, func(t *testing.T) {
+		t.Run(tt.latest+" vs "+tt.current, func(t *testing.T) {
+			t.Parallel()

133-141: Optional: t.Parallel here too.

-		t.Run(tt.name, func(t *testing.T) {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()

164-172: Optional: t.Parallel here too.

-		t.Run(tt.name, func(t *testing.T) {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fdfc4c and 679fa13.

📒 Files selected for processing (31)
  • .codecov.yml (1 hunks)
  • .github/dependabot.yml (1 hunks)
  • .github/workflows/build-and-publish.yml (4 hunks)
  • .github/workflows/dependency-review.yml (1 hunks)
  • .github/workflows/security.yml (1 hunks)
  • .github/workflows/test.yml (1 hunks)
  • .gitignore (1 hunks)
  • .golangci.yml (1 hunks)
  • .pre-commit-config.yaml (0 hunks)
  • CLAUDE.md (1 hunks)
  • README.md (1 hunks)
  • TESTING.md (1 hunks)
  • app_test.go (1 hunks)
  • autorun.go (1 hunks)
  • cross_platform_test.go (1 hunks)
  • icon.go (1 hunks)
  • logging.go (1 hunks)
  • main.go (1 hunks)
  • main_stub.go (1 hunks)
  • pure_funcs_test.go (1 hunks)
  • semver.go (1 hunks)
  • semver_test.go (1 hunks)
  • semver_utils_test.go (1 hunks)
  • theme.go (1 hunks)
  • tray_handlers.go (1 hunks)
  • ui_texts.go (1 hunks)
  • ui_texts_test.go (1 hunks)
  • updater.go (1 hunks)
  • updater_logic_test.go (1 hunks)
  • utils.go (1 hunks)
  • utils_test.go (1 hunks)
💤 Files with no reviewable changes (1)
  • .pre-commit-config.yaml
🧰 Additional context used
🧬 Code graph analysis (1)
app_test.go (1)
main.go (1)
  • NewApp (24-31)
🪛 actionlint (1.7.7)
.github/workflows/test.yml

163-163: input "skip-pkg-cache" is not defined in action "golangci/golangci-lint-action@v6". available inputs are "args", "cache-invalidation-interval", "github-token", "install-mode", "only-new-issues", "problem-matchers", "skip-cache", "skip-save-cache", "version", "working-directory"

(action)


164-164: input "skip-build-cache" is not defined in action "golangci/golangci-lint-action@v6". available inputs are "args", "cache-invalidation-interval", "github-token", "install-mode", "only-new-issues", "problem-matchers", "skip-cache", "skip-save-cache", "version", "working-directory"

(action)

.github/workflows/build-and-publish.yml

90-90: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL Analysis
🔇 Additional comments (21)
semver.go (1)

1-2: LGTM! Header comment follows convention.

The header comment addition aligns with the repository-wide convention being applied across all Go files in this PR.

.gitignore (1)

1-9: LGTM! Gitignore changes support CI/CD tooling.

The addition of the global dotfile ignore pattern with specific exceptions for CI/CD configuration files (.codecov.yml, .github/, .golangci.yml, .pre-commit-config.yaml) is appropriate for the new workflows and tooling being introduced.

icon.go (1)

1-2: LGTM! Header comment follows convention.

The header comment addition aligns with the repository-wide convention being applied across all Go files in this PR.

ui_texts.go (1)

1-2: LGTM! Header comment follows convention.

The header comment addition aligns with the repository-wide convention being applied across all Go files in this PR.

README.md (1)

4-9: LGTM! CI/CD badges enhance project visibility.

The addition of GitHub Actions workflow badges and coverage/quality metrics provides valuable information about project health and build status to users and contributors.

theme.go (1)

3-4: Build tags correctly gate Windows-only code.
Looks good; both modern and legacy tags are present and positioned correctly.

tray_handlers.go (1)

3-4: Build tags OK.
Gated for Windows correctly.

utils.go (3)

3-4: Build tags OK.
Windows-only as intended.


28-34: OK to open HKCU with requested access; error propagation is correct.
No changes needed.


36-38: getExePath is fine and testable.
Returns os.Executable unchanged. Good.

main.go (2)

3-4: Build tags OK.
Windows-only main is correctly gated.


23-31: Constructor behavior LGTM.
Defaulting empty version to v0.0.0 matches tests.

main_stub.go (1)

3-4: Non-Windows stub gating is correct.
Clear message and exit code 1. All good.

.codecov.yml (1)

8-44: Codecov config — verified

  • coverage.range "70...100" is valid; it defines color cutovers (below 70 = red, 70...100 = yellow, >=100 = green).
  • flags.paths accepts shell-style globs (not regex); "." selects the repository root (files under the repo root belong to that flag).
  • coverage.ignore accepts glob patterns (e.g. "/*_test.go", "/testdata/**") and will exclude matching files.
.github/dependabot.yml (1)

8-25: LGTM: solid schedules, scopes, review routing.

Weekly cadence, reviewers/labels, and rebase-strategy look good.

Consider adding “groups” to reduce PR noise (e.g., toolchain vs. libs). Want a snippet?

Also applies to: 26-44

.github/workflows/test.yml (1)

22-26: Confirm Go version availability across runners.

All jobs use Go 1.25. Ensure the repo builds/tests against that toolchain (and update go.mod if needed).

Would you like me to add a matrix to test 1.23–1.25 for smoother upgrades?

Also applies to: 88-91, 153-156

semver_test.go (1)

1-1: LGTM: header comment addition is fine.

semver_utils_test.go (1)

10-57: Good coverage for padVersionParts; table-driven style is clean.

cross_platform_test.go (1)

1-5: Build tags look correct (new + legacy).

pure_funcs_test.go (1)

14-16: If production funcs aren’t available cross-platform, add !windows build tags at file top.

Keeps this file compiling on Linux/macOS CI.

Apply:

+//go:build !windows
+// +build !windows
TESTING.md (1)

100-103: Coverage gating verified — 70% enforced

Test workflow (.github/workflows/test.yml — test-windows job) contains an explicit "Check coverage threshold" step that fails the job when total coverage < 70%; .codecov.yml also sets project and patch targets to 70% and has codecov.require_ci_to_pass: true.

Comment thread .github/workflows/security.yml Outdated
Comment thread .github/workflows/test.yml
Comment thread .golangci.yml Outdated
Comment thread app_test.go
Comment on lines +1 to +7
// app_test.go

package main

import (
"testing"
)

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

Gate this test to Windows; it depends on Windows-only code.
Without tags, this won’t compile on non-Windows (App/NewApp live in windows-gated files).

Apply:

 // app_test.go
+
+//go:build windows
+// +build windows
+
 package main
📝 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
// app_test.go
package main
import (
"testing"
)
// app_test.go
//go:build windows
// +build windows
package main
import (
"testing"
)
🤖 Prompt for AI Agents
In app_test.go around lines 1 to 7, the test references Windows-only code and
must be build-gated; add a Windows build tag at the top of the file (use the
modern //go:build windows line and the legacy // +build windows line immediately
before the package declaration) so the test only compiles on Windows, keeping
the rest of the file intact.

Comment thread pure_funcs_test.go
Comment on lines +14 to +53
func TestIsVersionNewerPure(t *testing.T) {
// Extracted logic test without external dependencies
testVersionNewer := func(latest, current string) bool {
latest = strings.TrimPrefix(latest, "v")
current = strings.TrimPrefix(current, "v")

latestParts := strings.Split(latest, ".")
currentParts := strings.Split(current, ".")

// Normalize to same length by padding with zeros
maxLen := len(latestParts)
if len(currentParts) > maxLen {
maxLen = len(currentParts)
}

// Pad with zeros
for len(latestParts) < maxLen {
latestParts = append(latestParts, "0")
}
for len(currentParts) < maxLen {
currentParts = append(currentParts, "0")
}

for i := 0; i < maxLen; i++ {
latestNum, err1 := strconv.Atoi(latestParts[i])
currentNum, err2 := strconv.Atoi(currentParts[i])
if err1 != nil || err2 != nil {
return false
}

if latestNum > currentNum {
return true
} else if latestNum < currentNum {
return false
}
}

return false
}

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

These tests reimplement isVersionNewer, so they don’t test production code. Replace with calls to isVersionNewer (and gate with !windows if needed).

Current approach can pass while the real implementation breaks.

Apply this diff:

-func TestIsVersionNewerPure(t *testing.T) {
-	// Extracted logic test without external dependencies
-	testVersionNewer := func(latest, current string) bool {
-		latest = strings.TrimPrefix(latest, "v")
-		current = strings.TrimPrefix(current, "v")
-		latestParts := strings.Split(latest, ".")
-		currentParts := strings.Split(current, ".")
-		// Normalize to same length by padding with zeros
-		maxLen := len(latestParts)
-		if len(currentParts) > maxLen {
-			maxLen = len(currentParts)
-		}
-		// Pad with zeros
-		for len(latestParts) < maxLen { latestParts = append(latestParts, "0") }
-		for len(currentParts) < maxLen { currentParts = append(currentParts, "0") }
-		for i := 0; i < maxLen; i++ {
-			latestNum, err1 := strconv.Atoi(latestParts[i])
-			currentNum, err2 := strconv.Atoi(currentParts[i])
-			if err1 != nil || err2 != nil { return false }
-			if latestNum > currentNum { return true } else if latestNum < currentNum { return false }
-		}
-		return false
-	}
+func TestIsVersionNewerPure(t *testing.T) {
@@
-			got := testVersionNewer(tt.latest, tt.current)
+			got := isVersionNewer(tt.latest, tt.current)
 			if got != tt.want {
 				t.Errorf("testVersionNewer(%q, %q) = %v; want %v", tt.latest, tt.current, got, tt.want)
 			}

If isVersionNewer isn’t available on all platforms, add at file top:

+//go:build !windows
+// +build !windows

And drop unused imports accordingly.

📝 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
func TestIsVersionNewerPure(t *testing.T) {
// Extracted logic test without external dependencies
testVersionNewer := func(latest, current string) bool {
latest = strings.TrimPrefix(latest, "v")
current = strings.TrimPrefix(current, "v")
latestParts := strings.Split(latest, ".")
currentParts := strings.Split(current, ".")
// Normalize to same length by padding with zeros
maxLen := len(latestParts)
if len(currentParts) > maxLen {
maxLen = len(currentParts)
}
// Pad with zeros
for len(latestParts) < maxLen {
latestParts = append(latestParts, "0")
}
for len(currentParts) < maxLen {
currentParts = append(currentParts, "0")
}
for i := 0; i < maxLen; i++ {
latestNum, err1 := strconv.Atoi(latestParts[i])
currentNum, err2 := strconv.Atoi(currentParts[i])
if err1 != nil || err2 != nil {
return false
}
if latestNum > currentNum {
return true
} else if latestNum < currentNum {
return false
}
}
return false
}
func TestIsVersionNewerPure(t *testing.T) {
// Use production implementation directly (don't reimplement isVersionNewer here).
// (preserve existing table of test cases named `tests`)
for _, tt := range tests {
got := isVersionNewer(tt.latest, tt.current)
if got != tt.want {
t.Errorf("isVersionNewer(%q, %q) = %v; want %v", tt.latest, tt.current, got, tt.want)
}
}
}
🤖 Prompt for AI Agents
In pure_funcs_test.go around lines 14 to 53, the test currently reimplements the
isVersionNewer logic instead of exercising the production function; replace the
local testVersionNewer closure with direct calls to the package's isVersionNewer
function so the tests validate real code, and if isVersionNewer is not built on
Windows add a build constraint (//go:build !windows) at the top of this test
file and remove any now-unused imports; ensure all test cases call
isVersionNewer and keep the same assertions.

Comment thread pure_funcs_test.go
Comment on lines +85 to +141
func TestPadVersionPartsPure(t *testing.T) {
padVersionParts := func(parts []string, targetLen int) []string {
result := make([]string, len(parts))
copy(result, parts)
for len(result) < targetLen {
result = append(result, "0")
}
return result
}

tests := []struct {
name string
parts []string
targetLen int
want []string
}{
{
name: "pad from 2 to 3",
parts: []string{"1", "2"},
targetLen: 3,
want: []string{"1", "2", "0"},
},
{
name: "pad from 1 to 3",
parts: []string{"1"},
targetLen: 3,
want: []string{"1", "0", "0"},
},
{
name: "no padding needed",
parts: []string{"1", "2", "3"},
targetLen: 3,
want: []string{"1", "2", "3"},
},
{
name: "already longer than target",
parts: []string{"1", "2", "3", "4"},
targetLen: 3,
want: []string{"1", "2", "3", "4"},
},
{
name: "empty parts",
parts: []string{},
targetLen: 2,
want: []string{"0", "0"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := padVersionParts(tt.parts, tt.targetLen)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("padVersionParts() = %v, want %v", got, tt.want)
}
})
}
}

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

Same issue: local padVersionParts duplicates production logic. Call the real padVersionParts instead.

Keeps tests meaningful and prevents drift.

Apply this diff:

-func TestPadVersionPartsPure(t *testing.T) {
-	padVersionParts := func(parts []string, targetLen int) []string {
-		result := make([]string, len(parts))
-		copy(result, parts)
-		for len(result) < targetLen {
-			result = append(result, "0")
-		}
-		return result
-	}
+func TestPadVersionPartsPure(t *testing.T) {
@@
-			got := padVersionParts(tt.parts, tt.targetLen)
+			got := padVersionParts(tt.parts, tt.targetLen)

Also remove now-unused imports (reflect stays).

📝 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
func TestPadVersionPartsPure(t *testing.T) {
padVersionParts := func(parts []string, targetLen int) []string {
result := make([]string, len(parts))
copy(result, parts)
for len(result) < targetLen {
result = append(result, "0")
}
return result
}
tests := []struct {
name string
parts []string
targetLen int
want []string
}{
{
name: "pad from 2 to 3",
parts: []string{"1", "2"},
targetLen: 3,
want: []string{"1", "2", "0"},
},
{
name: "pad from 1 to 3",
parts: []string{"1"},
targetLen: 3,
want: []string{"1", "0", "0"},
},
{
name: "no padding needed",
parts: []string{"1", "2", "3"},
targetLen: 3,
want: []string{"1", "2", "3"},
},
{
name: "already longer than target",
parts: []string{"1", "2", "3", "4"},
targetLen: 3,
want: []string{"1", "2", "3", "4"},
},
{
name: "empty parts",
parts: []string{},
targetLen: 2,
want: []string{"0", "0"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := padVersionParts(tt.parts, tt.targetLen)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("padVersionParts() = %v, want %v", got, tt.want)
}
})
}
}
func TestPadVersionPartsPure(t *testing.T) {
tests := []struct {
name string
parts []string
targetLen int
want []string
}{
{
name: "pad from 2 to 3",
parts: []string{"1", "2"},
targetLen: 3,
want: []string{"1", "2", "0"},
},
{
name: "pad from 1 to 3",
parts: []string{"1"},
targetLen: 3,
want: []string{"1", "0", "0"},
},
{
name: "no padding needed",
parts: []string{"1", "2", "3"},
targetLen: 3,
want: []string{"1", "2", "3"},
},
{
name: "already longer than target",
parts: []string{"1", "2", "3", "4"},
targetLen: 3,
want: []string{"1", "2", "3", "4"},
},
{
name: "empty parts",
parts: []string{},
targetLen: 2,
want: []string{"0", "0"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := padVersionParts(tt.parts, tt.targetLen)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("padVersionParts() = %v, want %v", got, tt.want)
}
})
}
}
🤖 Prompt for AI Agents
In pure_funcs_test.go around lines 85 to 141 the test defines a local
padVersionParts that duplicates production logic; replace the local function
with a direct call to the real padVersionParts from the production package
(remove the inner function declaration), update test calls to use the imported
production function, and ensure the production package is imported under the
correct name/path; finally remove any now-unused imports (keep reflect if still
used).

Comment thread pure_funcs_test.go
Comment on lines +143 to +172
func TestMaxPure(t *testing.T) {
max := func(a, b int) int {
if a > b {
return a
}
return b
}

tests := []struct {
name string
a int
b int
want int
}{
{"a greater than b", 5, 3, 5},
{"b greater than a", 2, 7, 7},
{"a equals b", 4, 4, 4},
{"negative numbers", -2, -5, -2},
{"zero and positive", 0, 1, 1},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := max(tt.a, tt.b)
if got != tt.want {
t.Errorf("max(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}

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

Same issue: local max duplicates production logic. Use max(a,b) from code.

Prevents false confidence.

Apply this diff:

-func TestMaxPure(t *testing.T) {
-	max := func(a, b int) int {
-		if a > b {
-			return a
-		}
-		return b
-	}
+func TestMaxPure(t *testing.T) {
@@
-			got := max(tt.a, tt.b)
+			got := max(tt.a, tt.b)
📝 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
func TestMaxPure(t *testing.T) {
max := func(a, b int) int {
if a > b {
return a
}
return b
}
tests := []struct {
name string
a int
b int
want int
}{
{"a greater than b", 5, 3, 5},
{"b greater than a", 2, 7, 7},
{"a equals b", 4, 4, 4},
{"negative numbers", -2, -5, -2},
{"zero and positive", 0, 1, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := max(tt.a, tt.b)
if got != tt.want {
t.Errorf("max(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
func TestMaxPure(t *testing.T) {
tests := []struct {
name string
a int
b int
want int
}{
{"a greater than b", 5, 3, 5},
{"b greater than a", 2, 7, 7},
{"a equals b", 4, 4, 4},
{"negative numbers", -2, -5, -2},
{"zero and positive", 0, 1, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := max(tt.a, tt.b)
if got != tt.want {
t.Errorf("max(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
🤖 Prompt for AI Agents
In pure_funcs_test.go around lines 143-172, the test redefines a local max
function that duplicates production logic; remove that local max definition and
call the actual max implementation from the package under test instead (delete
the local func, reference the package's max/Max function directly—adjust the
test package or import alias if needed so the production symbol is accessible),
keep the same table-driven cases and assertions, and run go test to confirm no
behavioral duplication remains.

Comment thread pure_funcs_test.go
Comment on lines +174 to +210
func TestUITextsValidation(t *testing.T) {
// Test menu titles structure without importing Windows dependencies
expectedTexts := map[string]string{
"AppName": "🔗 WinGoDarkTray",
"EnableAutorun": "Enable Autorun",
"Quit": "✕ Quit",
"EnableAutorunChecked": "✔ Autorun Enabled",
"EnableAutorunUnchecked": "✗ Autorun Disabled",
"ToggleAppToDark": "☾ Toggle app theme to Dark",
"ToggleAppToLight": "☼ Toggle app theme to Light",
"ToggleWinToDark": "☾ Toggle Windows theme to Dark",
"ToggleWinToLight": "☼ Toggle Windows theme to Light",
"ToggleBothToDark": "☾ Toggle both to Dark",
"ToggleBothToLight": "☼ Toggle both to Light",
"UpdateNow": "🔄 Update Now",
}

for name, expected := range expectedTexts {
if expected == "" {
t.Errorf("Expected text for %s should not be empty", name)
}
if name == "AppName" && !strings.Contains(expected, "WinGoDarkTray") {
t.Errorf("AppName should contain 'WinGoDarkTray', got: %s", expected)
}
}

// Test tooltip structure
defaultTooltip := "A windows app to toggle light and dark mode from the system tray"
errorTooltip := "An error occurred, please try again later."

if !strings.Contains(defaultTooltip, "toggle") || !strings.Contains(defaultTooltip, "windows") {
t.Error("Default tooltip should describe app purpose")
}
if errorTooltip == "" {
t.Error("Error tooltip should not be empty")
}
}

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

UI text “tests” only validate constants defined inside the test; they never exercise production values. Remove or import real constants.

Either import from the code (move texts to a cross-platform file) or drop this block.

Apply this diff to remove the non-valuable test:

-func TestUITextsValidation(t *testing.T) {
-	// Test menu titles structure without importing Windows dependencies
-	expectedTexts := map[string]string{
-		"AppName":                "🔗 WinGoDarkTray",
-		"EnableAutorun":          "Enable Autorun",
-		"Quit":                   "✕ Quit",
-		"EnableAutorunChecked":   "✔ Autorun Enabled",
-		"EnableAutorunUnchecked": "✗ Autorun Disabled",
-		"ToggleAppToDark":        "☾ Toggle app theme to Dark",
-		"ToggleAppToLight":       "☼ Toggle app theme to Light",
-		"ToggleWinToDark":        "☾ Toggle Windows theme to Dark",
-		"ToggleWinToLight":       "☼ Toggle Windows theme to Light",
-		"ToggleBothToDark":       "☾ Toggle both to Dark",
-		"ToggleBothToLight":      "☼ Toggle both to Light",
-	-	"UpdateNow":              "🔄 Update Now",
-	}
-	for name, expected := range expectedTexts {
-		if expected == "" {
-			t.Errorf("Expected text for %s should not be empty", name)
-		}
-		if name == "AppName" && !strings.contains(expected, "WinGoDarkTray") {
-			t.Errorf("AppName should contain 'WinGoDarkTray', got: %s", expected)
-		}
-	}
-	// Tooltips...
-}

If you want to keep UI text tests, move the text constants to a platform-agnostic file and import them here.

📝 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
func TestUITextsValidation(t *testing.T) {
// Test menu titles structure without importing Windows dependencies
expectedTexts := map[string]string{
"AppName": "🔗 WinGoDarkTray",
"EnableAutorun": "Enable Autorun",
"Quit": "✕ Quit",
"EnableAutorunChecked": "✔ Autorun Enabled",
"EnableAutorunUnchecked": "✗ Autorun Disabled",
"ToggleAppToDark": "☾ Toggle app theme to Dark",
"ToggleAppToLight": "☼ Toggle app theme to Light",
"ToggleWinToDark": "☾ Toggle Windows theme to Dark",
"ToggleWinToLight": "☼ Toggle Windows theme to Light",
"ToggleBothToDark": "☾ Toggle both to Dark",
"ToggleBothToLight": "☼ Toggle both to Light",
"UpdateNow": "🔄 Update Now",
}
for name, expected := range expectedTexts {
if expected == "" {
t.Errorf("Expected text for %s should not be empty", name)
}
if name == "AppName" && !strings.Contains(expected, "WinGoDarkTray") {
t.Errorf("AppName should contain 'WinGoDarkTray', got: %s", expected)
}
}
// Test tooltip structure
defaultTooltip := "A windows app to toggle light and dark mode from the system tray"
errorTooltip := "An error occurred, please try again later."
if !strings.Contains(defaultTooltip, "toggle") || !strings.Contains(defaultTooltip, "windows") {
t.Error("Default tooltip should describe app purpose")
}
if errorTooltip == "" {
t.Error("Error tooltip should not be empty")
}
}

Comment thread utils_test.go
Comment thread utils_test.go
Comment on lines +27 to +43
func TestOpenBrowserOnNonWindows(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skipping non-Windows test on Windows platform")
}

// This test verifies that openBrowser() handles non-Windows platforms gracefully
// Since showError() would be called, we can't easily test this without mocking
// But we can at least verify the function doesn't panic
defer func() {
if r := recover(); r != nil {
t.Errorf("openBrowser() panicked on non-Windows platform: %v", r)
}
}()

// Note: This will call showError() internally but shouldn't panic
openBrowser("https://example.com")
}

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

❓ Verification inconclusive

Non-Windows openBrowser test intention is good; ensure the symbol exists.
With the stub above, this will compile and assert no panic without side effects.

After adding the stub, run:


🏁 Script executed:

#!/bin/bash
# Validate tests compile on Linux and Windows (CI matrices)
go test ./... -c

Length of output: 597


Add stubs/implementations for missing symbols so tests compile.

go test -c failed with undefined symbols — add minimal test-safe stubs or implement the referenced functions/vars:

  • app_test.go:34 — undefined: NewApp
  • updater_logic_test.go:48,49,53,57,61 — undefined: githubReleasesAPI
  • utils_test.go:12 — undefined: getExePath
  • utils_test.go:42 — undefined: openBrowser

Place stubs in a *_test.go helper file in the same package (ensure openBrowser stub avoids side effects). After fixes re-run: go test ./... -c

🤖 Prompt for AI Agents
In utils_test.go around lines 27 to 43 and related tests, the package is missing
test-only symbols causing go test -c to fail (NewApp, githubReleasesAPI,
getExePath, openBrowser); add a new helper file named something like
test_helpers_stub_test.go in the same package that defines minimal, test-safe
stubs: implement NewApp returning a simple mock app, define githubReleasesAPI as
a lightweight interface/variable used by updater tests, implement getExePath
returning a deterministic path and no error, and implement openBrowser as a
no-op (or record the URL in a package-level variable) to avoid side effects;
keep implementations minimal and safe for concurrent tests, put them in _test.go
so they don’t ship in production, then re-run go test ./... -c.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (10)
.gitignore (1)

1-1: Don't blanket-ignore all dotfiles; add explicit exceptions for repo configs.

.* will hide useful config like .editorconfig and .gitattributes unless explicitly whitelisted.

Apply:

 .* 
+!.gitattributes
+!.editorconfig
+!.gitmodules
.golangci.yml (2)

6-10: Broaden lint coverage (staticcheck/revive/unused/ineffassign/misspell/errcheck).

These catch real issues missed by gofmt/goimports/govet alone.

Apply:

 linters:
   enable:
     - gofmt
     - goimports
     - govet
+    - staticcheck
+    - revive
+    - unused
+    - ineffassign
+    - misspell
+    - errcheck
+    - gosimple

3-4: Optional: set build tags if linting on non‑Windows runners.

Prevents false compile failures when Windows code is gated by tags.

Example:

 run:
   timeout: 5m
+  build-tags:
+    - windows
updater_stub.go (1)

3-4: Legacy build tag is redundant on modern Go.

Keep only //go:build if supporting Go ≥1.18; otherwise fine to keep both.

-// +build !windows
utils_stub.go (1)

8-18: Use a sentinel error for non‑Windows stubs (enable errors.Is checks).

Returning new fmt.Errorf each call hinders robust comparisons.

Apply:

-package main
-
-import "fmt"
+package main
+
+import "errors"
@@
-func getExePath() (string, error) {
-	return "", fmt.Errorf("getExePath not supported on non-Windows platforms")
-}
+var ErrUnsupportedPlatform = errors.New("not supported on non-Windows platforms")
+
+func getExePath() (string, error) {
+	return "", ErrUnsupportedPlatform
+}
@@
-func openBrowser(url string) error {
-	return fmt.Errorf("openBrowser not supported on non-Windows platforms")
-}
+func openBrowser(url string) error {
+	return ErrUnsupportedPlatform
+}

Ensure tests use errors.Is(..., ErrUnsupportedPlatform) rather than string matching.

app_stub.go (2)

29-32: Return a shared sentinel error (align with utils_stub).

Provides consistent detection across stubs.

Apply:

-import "fmt"
+import "errors"
@@
-func (a *App) Run() error {
-	return fmt.Errorf("App.Run not supported on non-Windows platforms")
-}
+var ErrUnsupportedPlatform = errors.New("not supported on non-Windows platforms")
+
+func (a *App) Run() error {
+	return ErrUnsupportedPlatform
+}

If both utils_stub.go and app_stub.go define ErrUnsupportedPlatform, consolidate into one (single source) or alias to avoid duplicates.


10-17: Interfaces as field types are fine for a stub; consider omitting fields entirely.

If tests don’t reference these, trimming them avoids leaking stub‑only surface.

.github/workflows/security.yml (3)

34-38: Pin tool installs; avoid floating @latest.

Floating installs reduce reproducibility.

Example:

-      run: go install golang.org/x/vuln/cmd/govulncheck@latest
+      run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.0
@@
-      run: go install honnef.co/go/tools/cmd/staticcheck@latest
+      run: go install honnef.co/go/tools/cmd/staticcheck@2024.1.1

Please confirm the latest stable versions before pinning.

Also applies to: 102-106


82-87: This step doesn’t audit vulnerabilities.

go list -json -deps only lists deps; it doesn’t check advisories. Either remove the job (govulncheck already covers it) or repurpose to integrity checks.

Apply:

-    - name: Run go mod audit (built-in vulnerability check)
-      run: |
-        echo "Checking for known vulnerabilities in dependencies..."
-        go list -json -deps ./... | findstr "Module" || echo "Dependency scan completed"
-        echo "Note: Using govulncheck for comprehensive vulnerability analysis"
-      continue-on-error: true
+    - name: Verify modules
+      run: |
+        go mod verify
+        go mod tidy -diff

21-39: Optional: run non‑Windows‑specific jobs on ubuntu‑latest to speed up.

govulncheck/staticcheck/go vet don’t require Windows and run faster/cheaper on Linux.

-    runs-on: windows-latest
+    runs-on: ubuntu-latest

Also applies to: 89-121

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88e3f81 and c6b2589.

📒 Files selected for processing (8)
  • .github/workflows/build-and-publish.yml (4 hunks)
  • .github/workflows/security.yml (1 hunks)
  • .github/workflows/test.yml (1 hunks)
  • .gitignore (1 hunks)
  • .golangci.yml (1 hunks)
  • app_stub.go (1 hunks)
  • updater_stub.go (1 hunks)
  • utils_stub.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/build-and-publish.yml
  • .github/workflows/test.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Static Security Analysis
  • GitHub Check: Dependency Vulnerability Scan
  • GitHub Check: CodeQL Analysis
  • GitHub Check: Test on Windows
🔇 Additional comments (3)
.github/workflows/security.yml (1)

41-65: CodeQL job looks good.

Pinned to v3 actions with proper permissions and categories.

.gitignore (1)

6-9: Pre‑commit filename mismatch; ensure both variants are unignored.

File: .gitignore
Lines: 6-9

Snippet:

!.codecov.yml
!.github/
!.golangci.yml
!.pre-commit-config.yml

Apply:

 !.codecov.yml
 !.github/
 !.golangci.yml
 !.pre-commit-config.yml
+!.pre-commit-config.yaml

Verification: prior check produced no output — confirm locally whether .pre-commit-config.yaml exists/tracked by running:
git ls-files --cached --others --exclude-standard | grep -E '^.pre-commit-config.(ya?ml)$' -n || true

updater_stub.go (1)

8-9: No action required — tests can access the unexported constant.
updater_stub.go (//go:build !windows) and updater.go define githubReleasesAPI; updater_logic_test.go is in package main (not main_test) and references it, so exporting the constant is unnecessary.

Likely an incorrect or invalid review comment.

Comment on lines +29 to +33
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.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

🧩 Analysis chain

Don’t hardcode Go 1.25; align with go.mod.

Use setup-go’s go-version-file to follow the repo’s declared version; avoids breakage if 1.25 isn’t available.

Apply:

-    - name: Set up Go
-      uses: actions/setup-go@v5
-      with:
-        go-version: '1.25'
+    - name: Set up Go
+      uses: actions/setup-go@v5
+      with:
+        go-version-file: 'go.mod'

Also applies to: 74-78, 97-101


🏁 Script executed:

#!/bin/bash
# Show declared Go toolchain in go.mod
rg -n '^go\s+\d+\.\d+' go.mod -n || cat go.mod

Length of output: 11


🌐 Web query:

Is Go 1.25 released and supported by actions/setup-go@v5? What is the latest stable Go version?

💡 Result:

Yes — Go 1.25 is released (go1.25.0 released August 12, 2025) and the official Go site shows the 1.25 release. [1]

actions/setup-go@v5 can install any Go release listed in the actions/go-versions manifest (and will download versions JIT if not in the runner image), so you can use setup-go@v5 with go-version: '1.25' (or 'stable') to get Go 1.25. [2][3]

Sources:

  • Go 1.25 release announcement and release notes. [1]
  • actions/setup-go README (v5) showing go-version usage and that it pulls versions from actions/go-versions. [2]
  • actions/go-versions repository (versions-manifest used by setup-go). [3]

Align workflow Go version to go.mod (don't hardcode 1.25).

go.mod declares "go 1.23.1" while the workflow hardcodes 1.25 — replace the hardcoded go-version with setup-go's go-version-file to follow go.mod and avoid CI/toolchain drift.

Applies to: .github/workflows/security.yml — lines 29-33, 74-78, 97-101.

-    - name: Set up Go
-      uses: actions/setup-go@v5
-      with:
-        go-version: '1.25'
+    - name: Set up Go
+      uses: actions/setup-go@v5
+      with:
+        go-version-file: 'go.mod'
📝 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
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
🤖 Prompt for AI Agents
In .github/workflows/security.yml around lines 29-33, 74-78 and 97-101, the
workflow currently hardcodes go-version: '1.25'; replace that with the setup-go
input that reads the Go version from the repository (use setup-go's
go-version-file pointing to go.mod) so the CI uses the go version declared in
go.mod; update each occurrence to use go-version-file: 'go.mod' and remove the
hardcoded go-version value.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (8)
icon_test.go (1)

61-70: Tighten failure semantics in TestIconEmbedded.

If icon is nil/empty, fail fast to avoid continuing a meaningless test run. Keep header mismatch as warning if that leniency is intentional.

@@
-      if icon == nil {
-        t.Error("icon byte slice is nil")
-      }
-      if len(icon) == 0 {
-        t.Error("icon byte slice is empty")
-      }
+      if icon == nil {
+        t.Fatal("icon byte slice is nil")
+      }
+      if len(icon) == 0 {
+        t.Fatal("icon byte slice is empty")
+      }

Also applies to: 71-80

tray_handlers_test.go (1)

29-62: Guard against implicit systray requirements in NewApp or menu item creation.

If NewApp touches systray API, this can hang/panic on CI. Prefer DI (menu factory) so tests use stubs; otherwise skip under -short/CI by default.

Minimal guard if you can’t refactor now:

 func TestAppMenuItemInitialization(t *testing.T) {
+  if testing.Short() {
+    t.Skip("skipping systray-adjacent test in -short")
+  }
   app := NewApp("test-version")

Longer-term: define an interface for menu creation (e.g., type MenuFactory interface { NewItem(title string) *systray.MenuItem }) and inject a fake in tests.

autorun_test.go (2)

14-38: UI test intentionally skipped — consider unit-testing logic via seam.

If updateAutorunUI has pure logic (title selection), expose that logic behind a small function and unit-test it without systray.


40-93: Good isolated HKCU test key usage; switch to t.Cleanup for robust teardown.

This ensures cleanup runs even if the test fails early.

@@
-      defer func() {
-        testKey.Close()
-        // Clean up the test key
-        registry.DeleteKey(registry.CURRENT_USER, "Software\\WinGoDarkTrayTest")
-      }()
+      t.Cleanup(func() {
+        testKey.Close()
+        _ = registry.DeleteKey(registry.CURRENT_USER, "Software\\WinGoDarkTrayTest")
+      })
logging_test.go (3)

53-103: Constant checks OK; optional: assert types to catch accidental type drift.

Not required, but you could assert reflect.TypeOf(...) if regressions have occurred before.


105-165: Console and safe logging tests are “no panic” smoke tests — consider capturing output to strengthen assertions.

You can capture stdout/stderr with os.Pipe and verify prefixes [INFO]/[WARNING]/[ERROR] if that’s the expected format.


220-247: Verify showNotificationWithFallback can run headless.

If it touches systray, consider skipping under -short or injecting a notifier that falls back to logging in tests.

theme_test.go (1)

96-111: Reading real theme from HKCU: treat as integration and skip by default under -short.

Avoid CI flakiness on machines without that key.

 func TestGetCurrentAppThemeMode(t *testing.T) {
+  if testing.Short() {
+    t.Skip("integration: reads HKCU theme registry")
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6b2589 and 1e3ba05.

📒 Files selected for processing (6)
  • .gitignore (1 hunks)
  • autorun_test.go (1 hunks)
  • icon_test.go (1 hunks)
  • logging_test.go (1 hunks)
  • theme_test.go (1 hunks)
  • tray_handlers_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .gitignore
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test on Windows
  • GitHub Check: Dependency Vulnerability Scan
  • GitHub Check: Static Security Analysis
  • GitHub Check: CodeQL Analysis
🔇 Additional comments (11)
tray_handlers_test.go (2)

12-27: Skip is appropriate for systray-dependent handler.

Acknowledged and appropriate to avoid flaky UI/threading in CI.


64-95: Version field checks look good.

autorun_test.go (1)

95-132: withAutorunRegistry read-access cases look fine.

Consider adding one negative case only if it won’t mutate real autorun state; otherwise keep it as-is to avoid side effects.

logging_test.go (3)

15-51: Event type name mapping assertions are solid.


167-192: Confirm showError has no blocking UI in CI.

If it can open dialogs, guard under -short/CI or route through a no-op notifier in tests.


194-218: Appropriate skip for tooltip test.

theme_test.go (4)

14-55: Theme constants test looks good.


57-94: Registry access wrapper tests are fine.


158-197: UI/registry-dependent methods correctly skipped.


199-220: Single-theme toggles correctly skipped.

icon_test.go (1)

9-50: Fix: make Windows-only constant checks Windows-only

Remove the autorunRegistryKey case from icon_test.go (ungated) and add a Windows-only test file icon_windows_test.go with the //go:build windows tag that asserts autorunRegistryKey == Software\Microsoft\Windows\CurrentVersion\Run. Repository contains Windows-gated files (e.g., utils.go, autorun.go); confirm where autorunRegistryKey is defined and gated before applying the change.

Comment thread theme_test.go
Comment on lines +113 to +156
func TestSetBothThemeModes(t *testing.T) {
tests := []struct {
name string
lightMode bool
}{
{
name: "set to light mode",
lightMode: true,
},
{
name: "set to dark mode",
lightMode: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Note: This test may fail if we don't have write access to the registry
// or if we're not running with sufficient privileges
err := setBothThemeModes(tt.lightMode)
if err != nil {
t.Logf("Expected behavior on restricted systems: %v", err)
// Don't fail the test as this is expected behavior in many environments
return
}

// If successful, verify the values were set correctly
appMode, err := getCurrentAppThemeMode()
if err != nil {
t.Logf("Could not verify app mode after setting: %v", err)
return
}

expectedMode := uint64(0)
if tt.lightMode {
expectedMode = 1
}

if appMode != expectedMode {
t.Errorf("After setBothThemeModes(%v), app mode = %d, want %d", tt.lightMode, appMode, expectedMode)
}
})
}
}

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: this test can change the user’s Windows theme (HKCU). Do not mutate user settings in unit tests.

setBothThemeModes writes AppsUseLightTheme/SystemUsesLightTheme; on a real Windows runner it will flip the user’s theme. Skip by default unless explicitly opted-in via env, or refactor to inject a registry interface and test against a fake.

Minimal safety guard:

@@
-import (
-  "testing"
-
-  "golang.org/x/sys/windows/registry"
-)
+import (
+  "os"
+  "testing"
+
+  "golang.org/x/sys/windows/registry"
+)
@@
 func TestSetBothThemeModes(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_THEME_REG_WRITE") != "1" {
+    t.Skip("integration: would modify HKCU theme; set ALLOW_THEME_REG_WRITE=1 to run explicitly")
+  }

Preferred: define a Registry interface (OpenKey/GetInteger/SetInteger) and pass a fake to setBothThemeModes in tests.

📝 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
func TestSetBothThemeModes(t *testing.T) {
tests := []struct {
name string
lightMode bool
}{
{
name: "set to light mode",
lightMode: true,
},
{
name: "set to dark mode",
lightMode: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Note: This test may fail if we don't have write access to the registry
// or if we're not running with sufficient privileges
err := setBothThemeModes(tt.lightMode)
if err != nil {
t.Logf("Expected behavior on restricted systems: %v", err)
// Don't fail the test as this is expected behavior in many environments
return
}
// If successful, verify the values were set correctly
appMode, err := getCurrentAppThemeMode()
if err != nil {
t.Logf("Could not verify app mode after setting: %v", err)
return
}
expectedMode := uint64(0)
if tt.lightMode {
expectedMode = 1
}
if appMode != expectedMode {
t.Errorf("After setBothThemeModes(%v), app mode = %d, want %d", tt.lightMode, appMode, expectedMode)
}
})
}
}
import (
"os"
"testing"
"golang.org/x/sys/windows/registry"
)
func TestSetBothThemeModes(t *testing.T) {
if testing.Short() || os.Getenv("ALLOW_THEME_REG_WRITE") != "1" {
t.Skip("integration: would modify HKCU theme; set ALLOW_THEME_REG_WRITE=1 to run explicitly")
}
tests := []struct {
name string
lightMode bool
}{
{
name: "set to light mode",
lightMode: true,
},
{
name: "set to dark mode",
lightMode: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Note: This test may fail if we don't have write access to the registry
// or if we're not running with sufficient privileges
err := setBothThemeModes(tt.lightMode)
if err != nil {
t.Logf("Expected behavior on restricted systems: %v", err)
// Don't fail the test as this is expected behavior in many environments
return
}
// If successful, verify the values were set correctly
appMode, err := getCurrentAppThemeMode()
if err != nil {
t.Logf("Could not verify app mode after setting: %v", err)
return
}
expectedMode := uint64(0)
if tt.lightMode {
expectedMode = 1
}
if appMode != expectedMode {
t.Errorf("After setBothThemeModes(%v), app mode = %d, want %d", tt.lightMode, appMode, expectedMode)
}
})
}
}
🤖 Prompt for AI Agents
In theme_test.go lines 113-156, the test mutates HKCU theme keys which can
change a user’s Windows theme; change the test to avoid touching real registry
by either (A) gating the test behind an explicit opt-in env var (e.g.,
SKIP_THEME_TESTS=false to run; otherwise call t.Skip) or (preferred) refactor
setBothThemeModes to accept a Registry interface (OpenKey/GetInteger/SetInteger)
and update production call sites to pass the real registry implementation; then
in this test inject a fake Registry that records reads/writes so you can assert
expected SetInteger calls and verify appMode without modifying the real
registry.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 28

♻️ Duplicate comments (1)
utils_test.go (1)

5-14: Compilation breaks on non-Windows due to unconditional windows/registry import and Windows-only symbols.

This file imports golang.org/x/sys/windows/registry and calls openRegistryKey, which aren’t available on non-Windows; typecheck fails.

Apply this diff to gate the file to Windows:

-// utils_test.go
+// utils_test.go
+//go:build windows
+// +build windows

Then move the non-Windows test(s) (e.g., TestOpenBrowserOnNonWindows) into a separate file compiled only on non-Windows (see snippet below).

🧹 Nitpick comments (26)
utils_coverage_test.go (2)

52-79: Non-Windows behavior test is inside a Windows-only file.

This test will never run on non-Windows due to the file’s //go:build windows tag.

Move it to a new file compiled only on non-Windows (utils_nonwindows_test.go) and drop the redundant GOOS checks here.


34-36: Redundant GOOS guard inside a Windows-only test file.

Given //go:build windows, this check is unnecessary.

Remove the guard to simplify.

advanced_coverage_test.go (1)

284-293: Use registry constants instead of magic numbers; fix duplicate access mask.

Both cases use 0x20019; prefer explicit constants and vary access as intended.

Apply this diff:

-			access: 0x20019,
+			access: registry.QUERY_VALUE,
@@
-			access: 0x20019,
+			access: registry.READ,
utils_test.go (1)

32-91: Relocate TestOpenBrowserOnNonWindows to a non-Windows test file.

Keep Windows-only tests here; run non-Windows behavior elsewhere.

Add this new file:

// utils_nonwindows_test.go
//go:build !windows
// +build !windows

package main

import (
	"os"
	"path/filepath"
	"runtime"
	"testing"
)

func TestGetExePath_NonWindows(t *testing.T) {
	path, err := getExePath()
	if err != nil {
		t.Fatalf("getExePath() error: %v", err)
	}
	if path == "" {
		t.Fatal("getExePath() returned empty path")
	}
	if !filepath.IsAbs(path) {
		t.Errorf("getExePath() returned relative path: %s", path)
	}
	if _, err := os.Stat(path); os.IsNotExist(err) {
		t.Errorf("getExePath() returned non-existent path: %s", path)
	}
}

func TestOpenBrowserOnNonWindows(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("non-Windows-only test")
	}
	defer func() {
		if r := recover(); r != nil {
			t.Errorf("openBrowser() panicked: %v", r)
		}
	}()
	openBrowser("https://example.com") // should be a no-op/stub without side effects
}
main_test.go (2)

12-27: Skip-only tests provide negligible value; consolidate or gate them.

These subtests only call t.Skip and inflate runtime/noise. Either:

  • Remove them, or
  • Collapse into a single placeholder test, or
  • Gate the whole file behind an opt-in tag/env to avoid running in CI by default.

Apply this minimal consolidation:

-func TestMainFunctionStructure(t *testing.T) {
-  ...
-  t.Skip("main() function starts full application - skipping to avoid side effects")
-}
-...
-func TestOnExitFunction(t *testing.T) { ... t.Skip("onExit function is not exported - cannot test directly") }
-...
-func TestApplicationInitializationFunctions(t *testing.T) { ... t.Skip("application initialization functions are not exported - cannot test directly") }
-...
-func TestCreateMenuItems(t *testing.T) { ... t.Skip("createMenuItems requires systray initialization") }
-...
-func TestStartEventHandlers(t *testing.T) { ... t.Skip("startEventHandlers creates goroutines and requires systray - skipping to avoid side effects") }
+func TestWindowsMain_SkippedPlaceholders(t *testing.T) {
+  t.Skip("Placeholder coverage for unexported/systray-dependent funcs (intentionally skipped).")
+}

Also applies to: 29-44, 46-70, 72-87, 89-105


106-143: Version test duplicates prod logic; extract a helper and test that.

Testing a locally reimplemented branch is brittle. Expose a small helper (e.g., resolveVersion(build string) string) and assert against it; this gives real signal and prevents drift.

Example prod helper (outside this file):

// version.go
package main
func resolveVersion(build string) string {
  if build == "" { return "v0.0.0" }
  return build
}

Then in this test:

- version := tt.buildVersion
- if version == "" {
-   version = "v0.0.0"
- }
- if version != tt.expectedVersion { ... }
+ if got := resolveVersion(tt.buildVersion); got != tt.expectedVersion {
+   t.Errorf("resolveVersion(%q) = %q, want %q", tt.buildVersion, got, tt.expectedVersion)
+ }
logging_coverage_test.go (3)

69-89: Brittle error-string equality; prefer Contains or sentinel.

Matching "unknown event type: 999" verbatim will flake if wording changes. Use substring or errors.Is with a sentinel.

-import (
-  "testing"
-  "time"
-
-  "golang.org/x/sys/windows/svc/eventlog"
-)
+import (
+  "strings"
+  "testing"
+  "time"
+  "golang.org/x/sys/windows/svc/eventlog"
+)
...
- if tt.eventType == 999 && err != nil {
-   expectedError := "unknown event type: 999"
-   if err.Error() != expectedError {
-     t.Errorf("logEvent() error = %v, want %v", err, expectedError)
-   }
- }
+ if tt.eventType == 999 && err != nil && !strings.Contains(err.Error(), "unknown event type") {
+   t.Errorf("logEvent() unexpected error: %v", err)
+ }

100-112: Gate admin-requiring paths to avoid CI breakage.

installEventLogSource usually needs elevation. Skip unless explicitly enabled.

+import "os"
...
 t.Run(tt.name, func(t *testing.T) {
+  if os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping admin-requiring test; set ALLOW_WINDOWS_E2E=1 to run.")
+  }
   err := installEventLogSource()
   _ = err
 })

176-189: Avoid showing real notifications by default.

This triggers OS UX in test runs. Gate it behind an env var.

+import "os"
...
 t.Run(tt.name, func(t *testing.T) {
+  if os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping notification test; set ALLOW_WINDOWS_E2E=1 to run.")
+  }
   defer func() { if r := recover(); r != nil { t.Errorf("... %v", r) } }()
   showNotificationWithFallback(tt.title, tt.message, tt.fallbackTooltip)
 })
autorun_test.go (3)

66-81: Use uniquely-named temp subkeys to prevent cross-run collisions.

A fixed key path can collide across parallel runs or locally. Append PID to the subkey.

- testKey, err := registry.OpenKey(registry.CURRENT_USER, "Software\\WinGoDarkTrayTest", registry.ALL_ACCESS)
+ keyPath := fmt.Sprintf("Software\\WinGoDarkTrayTest_%d", os.Getpid())
+ testKey, err := registry.OpenKey(registry.CURRENT_USER, keyPath, registry.ALL_ACCESS)
 ...
- testKey, _, err = registry.CreateKey(registry.CURRENT_USER, "Software\\WinGoDarkTrayTest", registry.ALL_ACCESS)
+ testKey, _, err = registry.CreateKey(registry.CURRENT_USER, keyPath, registry.ALL_ACCESS)
 ...
- registry.DeleteKey(registry.CURRENT_USER, "Software\\WinGoDarkTrayTest")
+ registry.DeleteKey(registry.CURRENT_USER, keyPath)

Add import:

-import (
+import (
   "fmt"
+  "os"
   "runtime"
   "testing"
   "golang.org/x/sys/windows/registry"
 )

170-178: Mirror unique key strategy in edge-case tests.

Apply the same dynamic subkey to avoid collisions.

- testKey, _, err := registry.CreateKey(registry.CURRENT_USER, "Software\\WinGoDarkTrayAutorunTest", registry.ALL_ACCESS)
+ keyPath := fmt.Sprintf("Software\\WinGoDarkTrayAutorunTest_%d", os.Getpid())
+ testKey, _, err := registry.CreateKey(registry.CURRENT_USER, keyPath, registry.ALL_ACCESS)
 ...
- registry.DeleteKey(registry.CURRENT_USER, "Software\\WinGoDarkTrayAutorunTest")
+ registry.DeleteKey(registry.CURRENT_USER, keyPath)

97-134: withAutorunRegistry tests: clarify expectations with an assertion.

These paths currently only mark “called”. Consider asserting that the callback executed under the expected access (e.g., read succeeds). If environment variance makes this flaky, leave as-is.

If you want, I can add a follow-up test that verifies a read-only query actually attempts to read a value and tolerates “value not found” specifically.

windows_native_test.go (3)

188-198: Prefer printable long strings over NUL padding.

string(make([]byte, 200)) creates NULs and can lead to odd behavior. Use Repeat.

- showNotificationWithFallback("Long Title", string(make([]byte, 200)), "Long tooltip")
+ showNotificationWithFallback("Long Title", strings.Repeat("x", 200), "Long tooltip")

206-276: Error-path tests also touch OS; gate them.

Registry access and getExePath/opening cwd can vary. Make these opt-in for CI stability.

 func TestWindowsErrorPaths(t *testing.T) {
+  if os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping Windows error-path E2E; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

278-328: Comprehensive API logging test: gate to avoid noisy CI.

Logging to event log/console in loops is noisy and environment-sensitive.

 func TestWindowsAPISafeInteractions(t *testing.T) {
+  if os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping Windows API logging E2E; set ALLOW_WINDOWS_E2E=1 to run.")
+  }
theme_coverage_test.go (2)

156-165: Unnecessary NewApp construction; remove side-effect risk.

This test only validates constants. Constructing NewApp adds systray coupling with no assertions.

Apply:

-			// Test that the constants can be used in theme operations
-			app := NewApp("test-version")
-			defer func() {
-				if r := recover(); r != nil {
-					t.Errorf("Using registry constant %s caused panic: %v", tt.name, r)
-				}
-			}()
-
-			// Skip actual function call as it requires systray initialization
-			_ = app
+			// No-op: constants validated above; avoid systray/app side effects

21-27: Placeholder tests that always Skip add noise.

If these are scaffolding for future systray E2E, mark them with a dedicated build tag (e.g., windows && e2e) or a TODO referencing an issue, so they don’t inflate test counts.

Also applies to: 48-54, 65-71, 82-88, 99-105

targeted_coverage_test.go (1)

98-105: Nil or zero-value systray.MenuItem usage risks blocking panics.

These code paths couple tests to systray internals. Prefer a minimal interface (Check/Uncheck/SetTitle/Show/Hide) and inject a fake in production code, or skip under -short.

Also applies to: 108-121, 123-136

comprehensive_coverage_test.go (2)

14-15: Import strings for resilient error assertions.

Apply:

 import (
 	"fmt"
 	"net/http"
 	"net/http/httptest"
 	"testing"
 
-	"golang.org/x/sys/windows/svc/eventlog"
+	"golang.org/x/sys/windows/svc/eventlog"
+	"strings"
 )

39-55: Brittle exact-match error assertion.

logEvent error text may change; assert using strings.Contains(err.Error(), "unknown event type") instead of full equality.

-					if err.Error() != expectedMsg {
-						t.Errorf("Expected error message %q, got %q", expectedMsg, err.Error())
-					}
+					if !strings.Contains(err.Error(), "unknown event type") {
+						t.Errorf("Expected error containing %q, got %q", "unknown event type", err.Error())
+					}
updater_coverage_test.go (2)

228-254: Punctuation nit in expected message.

"available!." reads odd; consider standardizing to "available!".


19-55: Sleep-based timing is brittle.

Use a context/WaitGroup or a start signal channel from the handler to make the test deterministic; avoid time.Sleep.

final_coverage_test.go (1)

285-311: Timeout test doesn’t exercise slow server.

Since production uses a fixed URL, the slowServer isn’t hit. After adding an injectable base URL, rewire this test; otherwise remove.

updater_test.go (3)

19-24: Hard-coded GitHub API path reduces flexibility.

If the repo is forked or moved, this test fails. Consider deriving from a single prod constant/var and overriding in tests when needed.


43-114: Entire test is skipped; prefer injectable design or remove.

Either make checkForUpdate accept a client/base URL so the mock server is used, or delete the dead test.


225-245: Timeout test isn’t tied to production client.

This asserts a locally constructed http.Client, not the one used in prod. Expose a constructor (e.g., newHTTPClient()) and assert that.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e3ba05 and aa477a2.

📒 Files selected for processing (19)
  • .gitignore (1 hunks)
  • advanced_coverage_test.go (1 hunks)
  • autorun_coverage_test.go (1 hunks)
  • autorun_test.go (1 hunks)
  • comprehensive_coverage_test.go (1 hunks)
  • final_coverage_test.go (1 hunks)
  • logging_coverage_test.go (1 hunks)
  • logging_test.go (1 hunks)
  • main_test.go (1 hunks)
  • systray_functional_test.go (1 hunks)
  • systray_integration_test.go (1 hunks)
  • targeted_coverage_test.go (1 hunks)
  • theme_coverage_test.go (1 hunks)
  • tray_coverage_test.go (1 hunks)
  • updater_coverage_test.go (1 hunks)
  • updater_test.go (1 hunks)
  • utils_coverage_test.go (1 hunks)
  • utils_test.go (1 hunks)
  • windows_native_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • logging_test.go
  • .gitignore
🧰 Additional context used
🪛 GitHub Check: Lint and Format Check
utils_test.go

[failure] 172-172:
undefined: openRegistryKey (typecheck)


[failure] 132-132:
undefined: openRegistryKey (typecheck)


[failure] 13-13:
could not import golang.org/x/sys/windows/registry (-: build constraints exclude all Go files in /home/runner/go/pkg/mod/golang.org/x/sys@v0.31.0/windows/registry) (typecheck)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test on Windows
🔇 Additional comments (7)
tray_coverage_test.go (1)

12-27: LGTM: Windows-gated scaffold test that intentionally skips.

Appropriate skip with a clear reason; harmless for CI and documents the dependency on systray initialization.

autorun_coverage_test.go (1)

13-41: LGTM: Basic executable-path check is correct and low-risk.

Validates non-empty path and existence; good coverage without side effects.

theme_coverage_test.go (1)

3-4: Dual build tags are correct.

Keeping both //go:build windows and // +build windows ensures compatibility across Go versions.

comprehensive_coverage_test.go (2)

206-214: Version comparison tests look good.


244-266: Constant accessibility check is fine.

final_coverage_test.go (1)

17-44: Goroutine start/stop pattern is acceptable.

Given current constraints, this is a reasonable smoke test.

updater_test.go (1)

247-276: Command structure validation is fine and side‑effect free.

Comment thread advanced_coverage_test.go
Comment on lines +39 to +44
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
openBrowser(tt.url)
})
}
}

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

Prevent browser side effects in advanced coverage.

Calling openBrowser() will open the default browser on Windows.

Apply this diff to mark these subtests as skipped (side-effectful paths are better covered via command-structure tests):

 		t.Run(tt.name, func(t *testing.T) {
-			openBrowser(tt.url)
+			t.Skip("Avoid side effects in CI; validate command composition separately.")
 		})
📝 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 _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
openBrowser(tt.url)
})
}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Skip("Avoid side effects in CI; validate command composition separately.")
})
}
}
🤖 Prompt for AI Agents
In advanced_coverage_test.go around lines 39 to 44, the subtests currently call
openBrowser(tt.url) which triggers the system browser; modify each subtest to
skip instead of performing the side effect by calling t.Skip("skipping
side-effectful test; covered via command-structure tests") at the start of the
subtest function (remove or guard the openBrowser call so it is not executed).

Comment thread advanced_coverage_test.go
Comment on lines +46 to +78
// TestLoggingAdvancedCoverage validates logging with diverse message formats
func TestLoggingAdvancedCoverage(t *testing.T) {
tests := []struct {
name string
message string
}{
{
name: "very long message",
message: "This is a very long log message that contains a lot of text to test how the logging system handles longer messages that might exceed normal buffer sizes or cause formatting issues in the event log system when displayed to administrators",
},
{
name: "message with unicode characters",
message: "Test with unicode: ñáéíóú αβγδε 中文测试 🔧⚙️",
},
{
name: "message with newlines",
message: "Line 1\nLine 2\nLine 3",
},
{
name: "message with tabs and spaces",
message: "Tabbed\ttext\twith multiple spaces",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logToConsole(eventlog.Info, tt.message)
logToConsole(eventlog.Warning, tt.message)
logToConsole(eventlog.Error, tt.message)
showError(tt.message)
})
}
}

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

Gate event log/UI side-effect tests behind -short.

Logging and showError may write to Windows Event Log/GUI; avoid in default CI runs.

Apply this diff at function start:

 func TestLoggingAdvancedCoverage(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping side-effectful logging tests in short mode")
+	}
📝 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
// TestLoggingAdvancedCoverage validates logging with diverse message formats
func TestLoggingAdvancedCoverage(t *testing.T) {
tests := []struct {
name string
message string
}{
{
name: "very long message",
message: "This is a very long log message that contains a lot of text to test how the logging system handles longer messages that might exceed normal buffer sizes or cause formatting issues in the event log system when displayed to administrators",
},
{
name: "message with unicode characters",
message: "Test with unicode: ñáéíóú αβγδε 中文测试 🔧⚙️",
},
{
name: "message with newlines",
message: "Line 1\nLine 2\nLine 3",
},
{
name: "message with tabs and spaces",
message: "Tabbed\ttext\twith multiple spaces",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logToConsole(eventlog.Info, tt.message)
logToConsole(eventlog.Warning, tt.message)
logToConsole(eventlog.Error, tt.message)
showError(tt.message)
})
}
}
// TestLoggingAdvancedCoverage validates logging with diverse message formats
func TestLoggingAdvancedCoverage(t *testing.T) {
if testing.Short() {
t.Skip("Skipping side-effectful logging tests in short mode")
}
tests := []struct {
name string
message string
}{
{
name: "very long message",
message: "This is a very long log message that contains a lot of text to test how the logging system handles longer messages that might exceed normal buffer sizes or cause formatting issues in the event log system when displayed to administrators",
},
{
name: "message with unicode characters",
message: "Test with unicode: ñáéíóú αβγδε 中文测试 🔧⚙️",
},
{
name: "message with newlines",
message: "Line 1\nLine 2\nLine 3",
},
{
name: "message with tabs and spaces",
message: "Tabbed\ttext\twith multiple spaces",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logToConsole(eventlog.Info, tt.message)
logToConsole(eventlog.Warning, tt.message)
logToConsole(eventlog.Error, tt.message)
showError(tt.message)
})
}
}
🤖 Prompt for AI Agents
In advanced_coverage_test.go around lines 46 to 78, the test writes to the
Windows Event Log / GUI and must be skipped during short CI runs; at the very
start of TestLoggingAdvancedCoverage check testing.Short() and call t.Skip with
a clear message (e.g., "skipping event log/UI side-effect tests in short mode")
so the body of the test is not executed in short mode.

Comment thread advanced_coverage_test.go
Comment on lines +80 to +105
// TestRegistryAdvancedOperations validates sequential registry operations
func TestRegistryAdvancedOperations(t *testing.T) {
tests := []struct {
name string
}{
{
name: "multiple sequential registry operations",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err1 := getCurrentAppThemeMode()
_ = err1

err2 := setBothThemeModes(true)
_ = err2

err3 := setBothThemeModes(false)
_ = err3

_, err4 := getCurrentAppThemeMode()
_ = err4
})
}
}

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

Theme/registry mutations should be opt-in.

setBothThemeModes() touches user/theme registry; don’t run by default.

Apply this diff at function start:

 func TestRegistryAdvancedOperations(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping registry-mutating tests in short mode")
+	}
📝 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
// TestRegistryAdvancedOperations validates sequential registry operations
func TestRegistryAdvancedOperations(t *testing.T) {
tests := []struct {
name string
}{
{
name: "multiple sequential registry operations",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err1 := getCurrentAppThemeMode()
_ = err1
err2 := setBothThemeModes(true)
_ = err2
err3 := setBothThemeModes(false)
_ = err3
_, err4 := getCurrentAppThemeMode()
_ = err4
})
}
}
// TestRegistryAdvancedOperations validates sequential registry operations
func TestRegistryAdvancedOperations(t *testing.T) {
if testing.Short() {
t.Skip("Skipping registry-mutating tests in short mode")
}
tests := []struct {
name string
}{
{
name: "multiple sequential registry operations",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err1 := getCurrentAppThemeMode()
_ = err1
err2 := setBothThemeModes(true)
_ = err2
err3 := setBothThemeModes(false)
_ = err3
_, err4 := getCurrentAppThemeMode()
_ = err4
})
}
}
🤖 Prompt for AI Agents
In advanced_coverage_test.go around lines 80 to 105, the test runs
registry-mutating calls by default; make these mutations opt-in by adding an
early guard at the start of TestRegistryAdvancedOperations that checks an opt-in
flag (for example an environment variable like ENABLE_REGISTRY_MUTATIONS or a
test-specific flag) and calls t.Skip or returns if the flag is not set, so the
subsequent get/setTheme registry calls are only executed when the opt-in is
explicitly provided.

Comment on lines +85 to +106
// TestOpenBrowserComprehensiveCoverage validates browser opening functionality
func TestOpenBrowserComprehensiveCoverage(t *testing.T) {
tests := []struct {
name string
url string
}{
{
name: "empty URL",
url: "",
},
{
name: "valid URL",
url: "https://example.com",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
openBrowser(tt.url)
})
}
}

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

openBrowser causes side effects on CI.

Launching browsers in tests is non-hermetic. Inject an opener function (or wrap exec.Command) and assert invocation; otherwise skip under -short.

 		t.Run(tt.name, func(t *testing.T) {
-			openBrowser(tt.url)
+			if testing.Short() {
+				t.Skip("skipping browser side effects in -short")
+			}
+			openBrowser(tt.url)
 		})
📝 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
// TestOpenBrowserComprehensiveCoverage validates browser opening functionality
func TestOpenBrowserComprehensiveCoverage(t *testing.T) {
tests := []struct {
name string
url string
}{
{
name: "empty URL",
url: "",
},
{
name: "valid URL",
url: "https://example.com",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
openBrowser(tt.url)
})
}
}
// TestOpenBrowserComprehensiveCoverage validates browser opening functionality
func TestOpenBrowserComprehensiveCoverage(t *testing.T) {
tests := []struct {
name string
url string
}{
{
name: "empty URL",
url: "",
},
{
name: "valid URL",
url: "https://example.com",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if testing.Short() {
t.Skip("skipping browser side effects in -short")
}
openBrowser(tt.url)
})
}
}
🤖 Prompt for AI Agents
In comprehensive_coverage_test.go around lines 85 to 106, the test calls
openBrowser which launches a real browser and causes side effects on CI; change
openBrowser to be injectable (e.g. accept an opener function argument or use a
package-level var like var browserOpener func(string) error defaulting to
exec.Command logic) so tests can supply a mock that records the URL and returns
nil, update the test to set that mock before calling openBrowser and assert the
mock was invoked with the expected URL instead of launching a browser, and add a
guard to skip the test when testing.Short() is true (t.Skip) to avoid running it
in short/CI runs.

Comment on lines +118 to +134
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mode, err := getCurrentAppThemeMode()
if err == nil {
if mode != 0 && mode != 1 {
t.Errorf("Invalid theme mode: %d", mode)
}
}

err = setBothThemeModes(true)
_ = err

err = setBothThemeModes(false)
_ = err
})
}
}

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

Registry mutation in unit test.

Mock registry operations or skip under -short.

🤖 Prompt for AI Agents
In comprehensive_coverage_test.go around lines 118 to 134, the test mutates
system registry via setBothThemeModes; change it to avoid real registry
mutations by either (1) mocking the registry operations used by
setBothThemeModes/getCurrentAppThemeMode (inject a fake registry client or use
an interface and pass a test double) and exercise the logic against the mock, or
(2) skip the test when running in short mode by checking testing.Short() at the
start of the subtest and calling t.Skipf with a clear reason; ensure any
real-registry calls are removed from the test path and that the mocked
implementation simulates success/failure cases the test expects.

Comment thread utils_coverage_test.go
Comment on lines +38 to +48
// Test that openBrowser doesn't panic
// We can't easily test the actual browser opening without side effects
defer func() {
if r := recover(); r != nil {
t.Errorf("openBrowser() panicked with URL %s: %v", tt.url, r)
}
}()

// Execute the function - it will try to open browser but we can test the code path
openBrowser(tt.url)
})

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

Avoid launching the real browser during tests.

openBrowser() on Windows will spawn the default browser, causing flaky CI and side effects.

Apply this diff to turn the subtest into a no-side-effects check:

-			// Execute the function - it will try to open browser but we can test the code path
-			openBrowser(tt.url)
+			// Avoid side effects in CI; command composition is validated elsewhere.
+			t.Skip("Skipping to avoid opening a real browser; see TestRundll32CommandStructure for validation.")
📝 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
// Test that openBrowser doesn't panic
// We can't easily test the actual browser opening without side effects
defer func() {
if r := recover(); r != nil {
t.Errorf("openBrowser() panicked with URL %s: %v", tt.url, r)
}
}()
// Execute the function - it will try to open browser but we can test the code path
openBrowser(tt.url)
})
// Test that openBrowser doesn't panic
// We can't easily test the actual browser opening without side effects
defer func() {
if r := recover(); r != nil {
t.Errorf("openBrowser() panicked with URL %s: %v", tt.url, r)
}
}()
// Avoid side effects in CI; command composition is validated elsewhere.
t.Skip("Skipping to avoid opening a real browser; see TestRundll32CommandStructure for validation.")
})
🤖 Prompt for AI Agents
In utils_coverage_test.go around lines 38-48, the subtest currently calls
openBrowser(tt.url) which launches the real browser on Windows and causes flaky
CI; update the test to avoid side effects by stubbing the browser opener:
refactor/openBrowser to call a package-level function variable (e.g., var
browserOpen = func(url string) { ... }) if not already, then in the test
temporarily replace that variable with a no-op implementation (save the
original, set noop, defer restore) and call openBrowser(tt.url); this lets you
exercise the code path and panic protection without actually spawning a browser.

Comment thread utils_test.go
Comment thread windows_native_test.go
Comment on lines +18 to +204
// TestWindowsNativeFunctionality validates Windows-specific API interactions under various permission scenarios
func TestWindowsNativeFunctionality(t *testing.T) {
tests := []struct {
name string
test func(*testing.T)
}{
{
name: "setBothThemeModes_actual_registry_operations",
test: func(t *testing.T) {
originalMode, err := getCurrentAppThemeMode()
if err != nil {
t.Logf("Could not read current theme mode: %v", err)
return
}

err = setBothThemeModes(true)
if err != nil {
t.Logf("Could not set light mode: %v", err)
}

err = setBothThemeModes(false)
if err != nil {
t.Logf("Could not set dark mode: %v", err)
}

// Restore original theme mode
if originalMode == 1 {
setBothThemeModes(true)
} else {
setBothThemeModes(false)
}
},
},
{
name: "toggleSingleTheme_registry_operations",
test: func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Logf("toggleSingleTheme panicked as expected: %v", r)
}
}()

app := NewApp("registry-test")
app.toggleSingleTheme(regValAppsUseLight)
app.toggleSingleTheme(regValSystemUsesLight)
},
},
{
name: "eventLogSourceExists_windows_check",
test: func(t *testing.T) {
exists := eventLogSourceExists()
t.Logf("Event log source exists: %v", exists)

// Verify consistent behavior across multiple calls
exists2 := eventLogSourceExists()
exists3 := eventLogSourceExists()
_ = exists2
_ = exists3
},
},
{
name: "installEventLogSource_windows_attempt",
test: func(t *testing.T) {
err1 := installEventLogSource()
t.Logf("Install attempt 1 result: %v", err1)

err2 := installEventLogSource()
t.Logf("Install attempt 2 result: %v", err2)

err3 := installEventLogSource()
t.Logf("Install attempt 3 result: %v", err3)
},
},
{
name: "isAdmin_windows_check",
test: func(t *testing.T) {
admin := isAdmin()
t.Logf("Running as admin: %v", admin)

// Verify consistent behavior across multiple calls
admin2 := isAdmin()
admin3 := isAdmin()
_ = admin2
_ = admin3
},
},
{
name: "logEvent_actual_windows_logging",
test: func(t *testing.T) {
logEvent(eventlog.Info, "Test info message from unit test")
logEvent(eventlog.Warning, "Test warning message from unit test")
logEvent(eventlog.Error, "Test error message from unit test")

// Test Unicode and extended length handling
logEvent(eventlog.Info, "Unicode test: ñáéíóú αβγδε 中文")
logEvent(eventlog.Warning, "Long message: "+strings.Repeat("A", 500))
},
},
{
name: "openBrowser_windows_execution",
test: func(t *testing.T) {
openBrowser("https://example.com")

// Test various URL formats and edge cases
openBrowser("")
openBrowser("invalid-url-format")
openBrowser("http://")
openBrowser("https://example.com/path?param=value&other=test")
},
},
{
name: "withThemeRegistry_error_scenarios",
test: func(t *testing.T) {
// Test registry operations with different permission levels
err1 := withThemeRegistry(registry.QUERY_VALUE, func(key registry.Key) error {
_, _, err := key.GetIntegerValue(regValAppsUseLight)
return err
})
t.Logf("Query value result: %v", err1)

err2 := withThemeRegistry(registry.SET_VALUE, func(key registry.Key) error {
return key.SetDWordValue(regValAppsUseLight, 1)
})
t.Logf("Set value result: %v", err2)

err3 := withThemeRegistry(registry.READ, func(key registry.Key) error {
_, _, err := key.GetIntegerValue(regValSystemUsesLight)
return err
})
t.Logf("Read access result: %v", err3)
},
},
{
name: "getExePath_windows_executable",
test: func(t *testing.T) {
path1, err1 := getExePath()
t.Logf("Executable path: %s, error: %v", path1, err1)

// Verify consistent behavior across multiple calls
path2, err2 := getExePath()
path3, err3 := getExePath()
_ = path2
_ = path3
_ = err2
_ = err3
},
},
{
name: "autorun_registry_operations",
test: func(t *testing.T) {
err1 := withAutorunRegistry(registry.QUERY_VALUE, func(key registry.Key) error {
enabled1 := isAutorunEnabled(key)
enabled2 := isAutorunEnabled(key)
enabled3 := isAutorunEnabled(key)
t.Logf("Autorun enabled checks: %v, %v, %v", enabled1, enabled2, enabled3)
return nil
})
t.Logf("Autorun query result: %v", err1)

err2 := withAutorunRegistry(registry.SET_VALUE, func(key registry.Key) error {
exePath, err := getExePath()
if err != nil {
return err
}
return key.SetStringValue("WinGoDarkTray", exePath)
})
t.Logf("Autorun set result: %v", err2)
},
},
{
name: "showNotificationWithFallback_windows_notifications",
test: func(t *testing.T) {
showNotificationWithFallback("Test Title", "Test message from unit test", "Test tooltip")
showNotificationWithFallback("Unicode Test", "Testing ñáéíóú αβγδε 中文", "Unicode tooltip")
showNotificationWithFallback("", "", "")
showNotificationWithFallback("Long Title", string(make([]byte, 200)), "Long tooltip")

// Allow notification processing time
time.Sleep(100 * time.Millisecond)
},
},
}

for _, tt := range tests {
t.Run(tt.name, tt.test)
}
}

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

Run Windows-native, side-effectful tests only when explicitly allowed.

This suite toggles theme, opens browser, touches event log, etc. Gate the entire test to avoid CI/user side effects.

 func TestWindowsNativeFunctionality(t *testing.T) {
+  if os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping Windows-native E2E; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In windows_native_test.go around lines 18 to 204, the test suite runs
side-effectful Windows-native operations unconditionally; gate the entire
TestWindowsNativeFunctionality so it only runs when explicitly allowed (e.g., an
environment variable or test flag) and only on Windows. Add a short early check
at the top of the test that: 1) returns t.Skipf(...) unless runtime.GOOS ==
"windows"; 2) reads an explicit opt-in (for example
os.Getenv("RUN_WINDOWS_NATIVE_TESTS") == "1" or a flag like -run_windows_native)
and calls t.Skipf(...) when not set; and 3) document the opt-in method in the
test comment so CI and developers must opt in before side effects occur.

Comment thread windows_native_test.go
Comment on lines +117 to +127
name: "openBrowser_windows_execution",
test: func(t *testing.T) {
openBrowser("https://example.com")

// Test various URL formats and edge cases
openBrowser("")
openBrowser("invalid-url-format")
openBrowser("http://")
openBrowser("https://example.com/path?param=value&other=test")
},
},

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

Avoid launching a browser in tests.

openBrowser will spawn external processes. Skip by default or mock.

- test: func(t *testing.T) {
+ test: func(t *testing.T) {
+   if os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+     t.Skip("Skipping browser launch; set ALLOW_WINDOWS_E2E=1 to run.")
+   }
    openBrowser("https://example.com")
    ...
📝 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
name: "openBrowser_windows_execution",
test: func(t *testing.T) {
openBrowser("https://example.com")
// Test various URL formats and edge cases
openBrowser("")
openBrowser("invalid-url-format")
openBrowser("http://")
openBrowser("https://example.com/path?param=value&other=test")
},
},
name: "openBrowser_windows_execution",
test: func(t *testing.T) {
if os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
t.Skip("Skipping browser launch; set ALLOW_WINDOWS_E2E=1 to run.")
}
openBrowser("https://example.com")
// Test various URL formats and edge cases
openBrowser("")
openBrowser("invalid-url-format")
openBrowser("http://")
openBrowser("https://example.com/path?param=value&other=test")
},
},
🤖 Prompt for AI Agents
In windows_native_test.go around lines 117 to 127, the test calls openBrowser
which spawns external processes; change the test to avoid launching a real
browser by either (A) skipping it by default (e.g., t.Skip("skipping external
process test") or gate with testing.Short()/env var) or (B) refactor openBrowser
to use an injectable command-runner or function variable and in this test
replace that runner with a no-op/mock that records the intended command and
arguments; update assertions to verify the mock was invoked with expected URLs
instead of actually launching a browser.

Comment thread windows_native_test.go
Comment on lines +166 to +186
name: "autorun_registry_operations",
test: func(t *testing.T) {
err1 := withAutorunRegistry(registry.QUERY_VALUE, func(key registry.Key) error {
enabled1 := isAutorunEnabled(key)
enabled2 := isAutorunEnabled(key)
enabled3 := isAutorunEnabled(key)
t.Logf("Autorun enabled checks: %v, %v, %v", enabled1, enabled2, enabled3)
return nil
})
t.Logf("Autorun query result: %v", err1)

err2 := withAutorunRegistry(registry.SET_VALUE, func(key registry.Key) error {
exePath, err := getExePath()
if err != nil {
return err
}
return key.SetStringValue("WinGoDarkTray", exePath)
})
t.Logf("Autorun set result: %v", err2)
},
},

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

Do not write to the real Autorun registry key in tests.

This persists system state (app starts on login). Replace with a temp HKCU subkey and validate via isAutorunEnabled(key).

-{
-  name: "autorun_registry_operations",
-  test: func(t *testing.T) {
-    err1 := withAutorunRegistry(registry.QUERY_VALUE, func(key registry.Key) error {
-      enabled1 := isAutorunEnabled(key)
-      enabled2 := isAutorunEnabled(key)
-      enabled3 := isAutorunEnabled(key)
-      t.Logf("Autorun enabled checks: %v, %v, %v", enabled1, enabled2, enabled3)
-      return nil
-    })
-    t.Logf("Autorun query result: %v", err1)
-
-    err2 := withAutorunRegistry(registry.SET_VALUE, func(key registry.Key) error {
-      exePath, err := getExePath()
-      if err != nil { return err }
-      return key.SetStringValue("WinGoDarkTray", exePath)
-    })
-    t.Logf("Autorun set result: %v", err2)
-  },
-},
+{
+  name: "autorun_registry_operations (temp key)",
+  test: func(t *testing.T) {
+    keyPath := fmt.Sprintf("Software\\WinGoDarkTrayTestAutorun_%d", os.Getpid())
+    testKey, _, err := registry.CreateKey(registry.CURRENT_USER, keyPath, registry.ALL_ACCESS)
+    if err != nil { t.Skipf("Cannot create test registry key: %v", err) }
+    defer func() {
+      testKey.Close()
+      registry.DeleteKey(registry.CURRENT_USER, keyPath)
+    }()
+    // Write and verify using the temp key only.
+    exePath, _ := getExePath()
+    _ = testKey.SetStringValue("WinGoDarkTray", exePath)
+    if !isAutorunEnabled(testKey) {
+      t.Errorf("expected autorun enabled for temp key")
+    }
+    _ = testKey.DeleteValue("WinGoDarkTray")
+    if isAutorunEnabled(testKey) {
+      t.Errorf("expected autorun disabled after deletion")
+    }
+  },
+},
📝 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
name: "autorun_registry_operations",
test: func(t *testing.T) {
err1 := withAutorunRegistry(registry.QUERY_VALUE, func(key registry.Key) error {
enabled1 := isAutorunEnabled(key)
enabled2 := isAutorunEnabled(key)
enabled3 := isAutorunEnabled(key)
t.Logf("Autorun enabled checks: %v, %v, %v", enabled1, enabled2, enabled3)
return nil
})
t.Logf("Autorun query result: %v", err1)
err2 := withAutorunRegistry(registry.SET_VALUE, func(key registry.Key) error {
exePath, err := getExePath()
if err != nil {
return err
}
return key.SetStringValue("WinGoDarkTray", exePath)
})
t.Logf("Autorun set result: %v", err2)
},
},
{
name: "autorun_registry_operations (temp key)",
test: func(t *testing.T) {
keyPath := fmt.Sprintf("Software\\WinGoDarkTrayTestAutorun_%d", os.Getpid())
testKey, _, err := registry.CreateKey(registry.CURRENT_USER, keyPath, registry.ALL_ACCESS)
if err != nil { t.Skipf("Cannot create test registry key: %v", err) }
defer func() {
testKey.Close()
registry.DeleteKey(registry.CURRENT_USER, keyPath)
}()
// Write and verify using the temp key only.
exePath, _ := getExePath()
_ = testKey.SetStringValue("WinGoDarkTray", exePath)
if !isAutorunEnabled(testKey) {
t.Errorf("expected autorun enabled for temp key")
}
_ = testKey.DeleteValue("WinGoDarkTray")
if isAutorunEnabled(testKey) {
t.Errorf("expected autorun disabled after deletion")
}
},
},
🤖 Prompt for AI Agents
In windows_native_test.go around lines 166 to 186, the test currently writes to
the real Autorun registry key; instead create a temporary HKCU subkey, use that
subkey for both the query and set operations, and clean it up afterwards.
Specifically: open registry.CURRENT_USER and call registry.CreateKey with a
unique test path (e.g. "Software\\<testname>-<random>" or based on t.Name()),
defer deleting that key, pass the created key into your isAutorunEnabled checks
and SetStringValue calls (or adapt the helper to accept a key) so the test
validates behavior against the temp subkey only and does not modify the real
autorun entry.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
updater.go (1)

49-80: Consider propagating context through HTTP client.

While the function accepts context, it doesn't use it for the HTTP request cancellation. Consider using http.NewRequestWithContext for better cancellation support.

Apply this diff to properly use context for HTTP request cancellation:

 func checkForUpdateWithContext(ctx context.Context, currentVersion string, updateNowItem *systray.MenuItem) {
   client := &http.Client{Timeout: 5 * time.Second}
-  resp, err := client.Get(githubReleasesAPI)
+  req, err := http.NewRequestWithContext(ctx, http.MethodGet, githubReleasesAPI, nil)
+  if err != nil {
+    logEvent(eventlog.Error, "Failed to create request: "+err.Error())
+    return
+  }
+  resp, err := client.Do(req)
   if err != nil {
     logEvent(eventlog.Error, "Failed to fetch latest release: "+err.Error())
     return
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a68bfab and 25a2ce0.

📒 Files selected for processing (5)
  • logging.go (2 hunks)
  • main.go (2 hunks)
  • systray_functional_test.go (1 hunks)
  • tray_handlers.go (1 hunks)
  • updater.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • main.go
🧰 Additional context used
🧬 Code graph analysis (2)
tray_handlers.go (1)
main.go (1)
  • App (16-22)
systray_functional_test.go (1)
main.go (1)
  • NewApp (25-32)
🪛 GitHub Check: Test on Windows
systray_functional_test.go

[failure] 10-10:
"sync" imported and not used

🪛 GitHub Actions: Test Suite
systray_functional_test.go

[error] 1-1: gofmt formatting check failed. Run 'gofmt -s -w .' to fix code style issues in this file.

🔇 Additional comments (14)
logging.go (3)

1-4: Good addition of Windows build constraints!

The build tags correctly restrict this file to Windows compilation, aligning with the Windows-specific functionality.


131-136: Excellent defensive programming for CI environments!

The panic recovery wrapper properly handles systray operations that may fail in headless/CI environments, with appropriate error logging that distinguishes these expected failures.


140-145: LGTM! Consistent error handling in the goroutine.

The deferred recovery in the goroutine ensures that tooltip reset failures are also handled gracefully in CI environments.

updater.go (4)

1-4: Good Windows-specific gating!

The build constraints properly restrict the updater functionality to Windows.


25-27: Clean delegation to context-aware variant.

The wrapper function maintains backward compatibility while enabling context-aware behavior.


29-43: Well-structured context-aware update handler!

The function properly handles context cancellation, enabling graceful shutdown and better testability. The select cases are correctly ordered.


45-47: Good backward compatibility wrapper!

Maintains existing API while delegating to the context-aware implementation.

tray_handlers.go (3)

1-4: Proper Windows build constraints!

The file is correctly gated for Windows-only compilation.


14-16: Clean backward compatibility wrapper!

The function maintains the existing API while enabling context-aware behavior.


18-35: Well-implemented context-aware menu handler!

The function properly handles context cancellation as the first case in the select statement, ensuring timely shutdown. All menu item click channels are correctly handled.

systray_functional_test.go (4)

18-86: Make systray E2E tests opt-in to prevent CI issues.

These tests can block or panic in headless CI environments. They should be opt-in via environment variable.


89-178: Make systray-dependent subtests opt-in.

These tests deliberately induce panics and can hang without a tray. They should be gated behind an environment variable.


181-249: Make initialization tests opt-in to avoid goroutine leaks.

The startEventHandlers function spawns goroutines that can leak in test environments.


251-309: Make real systray initialization opt-in.

This test attempts real systray UI initialization which can open UI windows and affect the OS.

Comment thread systray_functional_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (17)
main_test.go (6)

3-4: Build tags are fine; consider dropping legacy tag (optional).

You have both //go:build windows and // +build windows. Keeping both is OK for Go <1.17 compatibility. If you only target Go 1.17+, you can drop the legacy +build line.


12-27: Test adds no coverage; remove or mark as integration.

This test always skips and validates nothing. Prefer removing it or moving a real check under an integration build tag.

Apply this diff to remove the no-op test:

-func TestMainFunctionStructure(t *testing.T) {
-	tests := []struct {
-		name string
-	}{
-		{
-			name: "main function initialization",
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			// Skip actual main execution as it would start the full application
-			t.Skip("main() function starts full application - skipping to avoid side effects")
-		})
-	}
-}

29-44: onExit is testable in the same package; make this a real test.

Since this file uses package main, you can call unexported onExit() directly. Replace the skip with a panic-safety check.

Apply this diff:

-func TestOnExitFunction(t *testing.T) {
-	tests := []struct {
-		name string
-	}{
-		{
-			name: "onExit function call",
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			// Skip this test as onExit is not exported
-			t.Skip("onExit function is not exported - cannot test directly")
-		})
-	}
-}
+func TestOnExitFunction(t *testing.T) {
+	t.Run("onExit does not panic", func(t *testing.T) {
+		defer func() {
+			if r := recover(); r != nil {
+				t.Fatalf("onExit panicked: %v", r)
+			}
+		}()
+		onExit()
+	})
+}

46-70: Skip-only scaffolding; convert to integration or delete.

These subtests always skip and assert nothing. Either (a) move them to main_integration_test.go with //go:build windows && integration, or (b) delete until you can inject dependencies to unit-test them without systray.

I can sketch a small dependency-injection seam (wrapping systray calls) so these can be unit tested—say the word.


72-87: Menu creation test should be integration or DI-backed.

As written it always skips. If you wrap systray calls behind an interface and inject a fake in tests, you can assert items/ordering without starting systray. Otherwise, gate it under an integration tag.


89-104: Event handler test needs cancellation/DI; otherwise mark as integration.

Introduce context-driven handlers or injectable channels so tests can run deterministically and exit cleanly. Without that, keep as an integration test.

advanced_coverage_test.go (1)

283-294: Use registry constants instead of magic numbers; fix duplicate access.

Both cases currently use 0x20019; use QUERY_VALUE for query and READ for read.

Apply this diff:

   tests := []struct {
     name    string
     keyPath string
     access  uint32
   }{
     {
       name:    "query access to theme registry",
       keyPath: `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`,
-      access:  0x20019,
+      access:  registry.QUERY_VALUE,
     },
     {
       name:    "read access to theme registry",
       keyPath: `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize`,
-      access:  0x20019,
+      access:  registry.READ,
     },
   }
updater_test.go (2)

99-112: Skip before spinning up the mock server.

You create httptest.Server and then skip; move t.Skip earlier to avoid resource use.

Apply this diff:

   for _, tt := range tests {
     t.Run(tt.name, func(t *testing.T) {
-      // Create mock server
-      server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-        w.WriteHeader(tt.statusCode)
-        if tt.mockResponse != nil {
-          json.NewEncoder(w).Encode(tt.mockResponse)
-        }
-      }))
-      defer server.Close()
-
-      // Skip this test as it requires systray initialization and modifies global state
-      t.Skip("checkForUpdate requires systray initialization and modifies global state")
+      t.Skip("checkForUpdate requires systray initialization and modifies global state")
+      // The code below intentionally not executed when skipped.
+      server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+        w.WriteHeader(tt.statusCode)
+        if tt.mockResponse != nil {
+          json.NewEncoder(w).Encode(tt.mockResponse)
+        }
+      }))
+      defer server.Close()
     })
   }

43-114: Make checkForUpdate testable by injecting deps.

Refactor production checkForUpdate to accept an http.Client and base URL (or a ReleaseFetcher interface) so this test can actually hit httptest.Server without systray/global state.

I can sketch the interface and updates if you want.

theme_test.go (2)

8-12: Import os for opt‑in gating.

Apply this diff:

 import (
+  "os"
   "testing"

   "golang.org/x/sys/windows/registry"
 )

96-111: Gate registry reads under -short.

Even reads can fail on CI; skip in short mode.

Apply this diff:

 func TestGetCurrentAppThemeMode(t *testing.T) {
+  if testing.Short() {
+    t.Skip("skipping registry access in -short mode")
+  }
   // This test attempts to read the actual theme registry
windows_native_test.go (2)

207-211: Gate error‑path registry tests behind opt‑in too.

Apply this diff:

 func TestWindowsErrorPaths(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping Windows registry permission tests; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

279-283: Gate logging flood tests behind opt‑in.

Apply this diff:

 func TestWindowsAPISafeInteractions(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping verbose Windows API interactions in short/CI runs.")
+  }
targeted_coverage_test.go (3)

8-13: Import os for gating.

Apply this diff:

 import (
+  "os"
   "testing"
   "time"

   "github.com/getlantern/systray"
 )

178-186: Also gate setupEventLog extended tests.

Apply this diff:

 func TestSetupEventLogExtended(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("requires Windows event log; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

272-297: Gate direct registry operations.

Apply this diff:

 func TestRegistryErrorScenarios(t *testing.T) {
+  if testing.Short() {
+    t.Skip("skipping registry access in -short mode")
+  }
systray_functional_test.go (1)

8-14: Import os for opt‑in gating.

Apply this diff:

 import (
   "context"
+  "os"
   "testing"
   "time"

   "github.com/getlantern/systray"
 )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25a2ce0 and e854be9.

📒 Files selected for processing (19)
  • advanced_coverage_test.go (1 hunks)
  • autorun_coverage_test.go (1 hunks)
  • autorun_test.go (1 hunks)
  • comprehensive_coverage_test.go (1 hunks)
  • final_coverage_test.go (1 hunks)
  • logging_coverage_test.go (1 hunks)
  • logging_test.go (1 hunks)
  • main_test.go (1 hunks)
  • systray_functional_test.go (1 hunks)
  • systray_integration_test.go (1 hunks)
  • targeted_coverage_test.go (1 hunks)
  • theme_coverage_test.go (1 hunks)
  • theme_test.go (1 hunks)
  • tray_coverage_test.go (1 hunks)
  • tray_handlers_test.go (1 hunks)
  • updater_coverage_test.go (1 hunks)
  • updater_test.go (1 hunks)
  • utils_coverage_test.go (1 hunks)
  • windows_native_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • logging_test.go
  • tray_handlers_test.go
  • utils_coverage_test.go
  • tray_coverage_test.go
  • updater_coverage_test.go
  • comprehensive_coverage_test.go
  • autorun_coverage_test.go
  • autorun_test.go
  • systray_integration_test.go
  • final_coverage_test.go
  • logging_coverage_test.go
🧰 Additional context used
🧬 Code graph analysis (4)
updater_test.go (2)
updater.go (3)
  • runUpdateCommand (75-81)
  • checkForUpdate (32-63)
  • showUpdateNotification (66-73)
semver_test.go (1)
  • TestIsVersionNewer (5-38)
theme_test.go (1)
theme.go (8)
  • a (111-147)
  • a (84-109)
  • setBothThemeModes (38-52)
  • a (153-155)
  • withThemeRegistry (17-24)
  • a (149-151)
  • getCurrentAppThemeMode (27-35)
  • a (54-81)
main_test.go (1)
main.go (7)
  • main (31-34)
  • a (38-47)
  • a (62-85)
  • a (92-103)
  • toggleSystemItem (10-16)
  • onExit (36-36)
  • a (49-52)
systray_functional_test.go (1)
main.go (1)
  • NewApp (25-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL Analysis
  • GitHub Check: Test on Windows
🔇 Additional comments (16)
main_test.go (1)

106-143: Use the real constructor (NewApp) in tests — verify it has no systray side effects.

Don't re-implement version logic in the test; call NewApp(tt.buildVersion) and assert app.version, running subtests in parallel (tt := tt; t.Parallel()). I couldn't locate NewApp with the provided search and observed many tests skipping because of systray initialization — confirm NewApp exists, only sets fields (no systray or global side effects), and that the default version is "v0.0.0" before applying this change in main_test.go:106-143.

theme_test.go (2)

57-94: LGTM: withThemeRegistry happy‑path coverage.


113-156: Critical: test mutates user theme; make opt‑in.

Do not write HKCU by default. Gate behind -short and an explicit env var.

Apply this diff:

 func TestSetBothThemeModes(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_THEME_REG_WRITE") != "1" {
+    t.Skip("integration: would modify HKCU theme; set ALLOW_THEME_REG_WRITE=1 to run explicitly")
+  }
theme_coverage_test.go (1)

116-129: Skip registry access under -short.

Avoid flakiness and side effects in CI.

Apply this diff:

   for _, tt := range tests {
     t.Run(tt.name, func(t *testing.T) {
+      if testing.Short() {
+        t.Skip("skipping registry access in -short mode")
+      }
       // Test getCurrentAppThemeMode error handling
advanced_coverage_test.go (3)

39-42: Skip browser‑launching tests to avoid CI side effects.

Launching the real browser in unit tests is unsafe; skip and validate command composition elsewhere.

Apply this diff:

 for _, tt := range tests {
   t.Run(tt.name, func(t *testing.T) {
-    openBrowser(tt.url)
+    t.Skip("Avoid side effects in CI; validate command composition separately.")
   })
 }

47-48: Gate event log/UI side‑effect tests behind -short.

These write to event log / show GUI; skip in short runs.

Apply this diff:

 func TestLoggingAdvancedCoverage(t *testing.T) {
+  if testing.Short() {
+    t.Skip("Skipping side-effectful logging tests in short mode")
+  }

81-82: Do not mutate registry by default; gate under -short.

setBothThemeModes writes HKCU; make opt‑in.

Apply this diff:

 func TestRegistryAdvancedOperations(t *testing.T) {
+  if testing.Short() {
+    t.Skip("Skipping registry-mutating tests in short mode")
+  }
windows_native_test.go (2)

19-23: Gate entire Windows‑native E2E suite behind opt‑in.

Prevents browser launches, registry/event log writes, and other side effects during CI.

Apply this diff:

 func TestWindowsNativeFunctionality(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping Windows-native E2E; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

166-186: Avoid writing to real Autorun key; use a temp HKCU subkey.

Use a throwaway subkey and clean up after to avoid persisting autorun state.

Apply this diff:

-    err2 := withAutorunRegistry(registry.SET_VALUE, func(key registry.Key) error {
-      exePath, err := getExePath()
-      if err != nil {
-        return err
-      }
-      return key.SetStringValue("WinGoDarkTray", exePath)
-    })
-    t.Logf("Autorun set result: %v", err2)
+    keyPath := fmt.Sprintf("Software\\WinGoDarkTrayTestAutorun_%d", os.Getpid())
+    testKey, _, err := registry.CreateKey(registry.CURRENT_USER, keyPath, registry.ALL_ACCESS)
+    if err != nil {
+      t.Skipf("Cannot create test registry key: %v", err)
+    }
+    defer func() {
+      testKey.Close()
+      _ = registry.DeleteKey(registry.CURRENT_USER, keyPath)
+    }()
+    exePath, _ := getExePath()
+    _ = testKey.SetStringValue("WinGoDarkTray", exePath)
+    if !isAutorunEnabled(testKey) {
+      t.Errorf("expected autorun enabled for temp key")
+    }
+    _ = testKey.DeleteValue("WinGoDarkTray")
+    if isAutorunEnabled(testKey) {
+      t.Errorf("expected autorun disabled after deletion")
+    }
targeted_coverage_test.go (3)

16-21: Gate systray/side‑effect tests behind -short and env.

These paths can panic/hang and touch OS state.

Apply this diff:

 func TestTargetedCoverageImprovement(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("requires systray/Windows E2E; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

239-271: Avoid real network/API calls in unit tests.

checkForUpdate hits GitHub; inject http.Client/baseURL or skip by default.

Apply this diff:

 func TestUpdaterEdgeCases(t *testing.T) {
+  if testing.Short() {
+    t.Skip("networked test; skip in unit runs")
+  }

And consider refactoring production code to accept a client/baseURL for httptest.Server.


206-237: Do not toggle themes/registry by default.

Apply this diff:

 func TestThemeToggleEdgeCases(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("skipping registry mutation tests; set ALLOW_WINDOWS_E2E=1 to run.")
+  }
systray_functional_test.go (4)

16-21: Make functional systray test opt‑in to avoid UI/hangs.

Apply this diff:

 func TestSystrayFunctionsWithTimeout(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping systray E2E; set ALLOW_WINDOWS_E2E=1 to run.")
+  }
   // Use a very short timeout to avoid hanging the test suite

179-184: Gate initialization paths that spawn goroutines.

Apply this diff:

 func TestApplicationInitialization(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping initialization E2E; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

87-93: Gate systray‑dependent subtests.

Apply this diff:

 func TestSystrayDependentFunctions(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping systray-dependent tests; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

250-254: Skip real systray UI init by default.

Apply this diff:

 func TestSystrayInitializationAttempt(t *testing.T) {
+  if testing.Short() || os.Getenv("ALLOW_WINDOWS_E2E") != "1" {
+    t.Skip("Skipping real systray init; set ALLOW_WINDOWS_E2E=1 to run.")
+  }

@nett00n
nett00n merged commit 4e97393 into main Sep 23, 2025
11 of 12 checks passed
@nett00n
nett00n deleted the feature/add-unit-tests branch September 24, 2025 07:38
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.

2 participants