Skip to content

Conversation

@DropSnorz
Copy link
Owner

No description provided.

@DropSnorz DropSnorz self-assigned this Aug 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 6, 2025

Walkthrough

A new preference-controlled option for synchronizing file statistics after plugin synchronization was introduced, involving updates to application constants, controller logic, FXML layouts, and plugin task scheduling. Additionally, minor UI text changes were made in the donation dialog and plugin sync options.

Changes

Cohort / File(s) Change Summary
Application Defaults Constant Addition
owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java
Added SYNC_FILE_STAT public static final String constant for new preference control.
Options Controller Enhancements
owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java
Added checkbox and label fields, preference binding, and UI logic for new sync file stat option; updated label management and refresh logic.
Plugin Task Scheduling Logic
owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java
Modified scheduling of file stat sync task to be conditional on the new preference flag.
Options View Layout Updates
owlplug-client/src/main/resources/fxml/OptionsView.fxml
Updated FXML: added new checkbox and label for file stat sync, adjusted layout and texts, and flattened VBox structure.
Donate Dialog Text Update
owlplug-client/src/main/resources/fxml/dialogs/DonateView.fxml
Changed label and button text from "Contribute" to "Donate."

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant OptionsController
    participant Preferences
    participant PluginTaskFactory

    User->>OptionsController: Toggle "Analyze file statistics" checkbox
    OptionsController->>Preferences: Update SYNC_FILE_STAT preference

    User->>PluginTaskFactory: Trigger plugin sync
    PluginTaskFactory->>Preferences: Check SYNC_FILE_STAT
    alt SYNC_FILE_STAT is true
        PluginTaskFactory->>PluginTaskFactory: Schedule file stat sync task
    else SYNC_FILE_STAT is false
        PluginTaskFactory-->>PluginTaskFactory: Skip file stat sync task
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 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 0f8b662 and 25acc6a.

📒 Files selected for processing (5)
  • owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java (1 hunks)
  • owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java (6 hunks)
  • owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java (1 hunks)
  • owlplug-client/src/main/resources/fxml/OptionsView.fxml (3 hunks)
  • owlplug-client/src/main/resources/fxml/dialogs/DonateView.fxml (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: in the owlplug codebase, thread safety is not required for the static contributors field in applicat...
Learnt from: DropSnorz
PR: DropSnorz/OwlPlug#335
File: owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java:51-51
Timestamp: 2025-07-21T17:10:34.466Z
Learning: In the OwlPlug codebase, thread safety is not required for the static contributors field in ApplicationDefaults.java, as confirmed by the project maintainer.

Applied to files:

  • owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java
📚 Learning: in the owlplug javafx application, thread safety is not required for the getcontributors() method in...
Learnt from: DropSnorz
PR: DropSnorz/OwlPlug#335
File: owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java:245-270
Timestamp: 2025-07-21T17:10:41.671Z
Learning: In the OwlPlug JavaFX application, thread safety is not required for the getContributors() method in ApplicationDefaults.java, as confirmed by the project maintainer DropSnorz.

Applied to files:

  • owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java
📚 Learning: the `creatorlink` in `packageinfocontroller.java` is guaranteed to be initialized before use due to ...
Learnt from: DropSnorz
PR: DropSnorz/OwlPlug#0
File: :0-0
Timestamp: 2024-07-26T20:55:41.458Z
Learning: The `creatorLink` in `PackageInfoController.java` is guaranteed to be initialized before use due to the FXML loading process and the application's structure, making a null check unnecessary in this context.

Applied to files:

  • owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java
📚 Learning: in the disableplugindialogcontroller, the admin privileges warning is always shown when disabling a ...
Learnt from: DropSnorz
PR: DropSnorz/OwlPlug#280
File: owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/DisablePluginDialogController.java:47-49
Timestamp: 2025-01-09T18:03:46.895Z
Learning: In the DisablePluginDialogController, the admin privileges warning is always shown when disabling a plugin, regardless of the actual file permissions, as implementing permission checks is complex and OS-dependent.

Applied to files:

  • owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java
📚 Learning: null checks for plugin fields in dialog controllers are not required as per the repository owner's p...
Learnt from: DropSnorz
PR: DropSnorz/OwlPlug#280
File: owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/DisablePluginDialogController.java:28-28
Timestamp: 2025-01-09T18:04:19.678Z
Learning: Null checks for plugin fields in dialog controllers are not required as per the repository owner's preference.

Applied to files:

  • owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java
⏰ 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: Codacy Static Code Analysis
🔇 Additional comments (12)
owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java (1)

120-120: LGTM! Clean constant addition.

The new SYNC_FILE_STAT constant follows the established naming conventions and is positioned appropriately with other preference keys.

owlplug-client/src/main/resources/fxml/dialogs/DonateView.fxml (2)

40-40: LGTM! Text clarification improves user experience.

The change from "Contribute" to "Donate" provides clearer messaging about the action being requested.


44-44: LGTM! Consistent button text.

The button text now matches the updated label text, providing consistent messaging throughout the dialog.

owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java (1)

117-125: LGTM! Well-implemented preference-controlled feature toggle.

The conditional scheduling of file stat synchronization is properly implemented:

  • Uses the correct preference key with appropriate default value (true)
  • Maintains backward compatibility by defaulting to existing behavior
  • Preserves the existing scoped/unscoped task selection logic
  • Clean, readable conditional structure
owlplug-client/src/main/java/com/owlplug/core/controllers/OptionsController.java (4)

65-65: LGTM! Consistent field declaration.

The new checkbox field follows the established naming conventions and patterns used by other UI controls in this controller.


167-169: LGTM! Proper preference binding.

The preference listener follows the same pattern as other checkboxes in the controller, correctly binding to the SYNC_FILE_STAT preference key.


174-174: LGTM! Appropriate visibility control.

The visibility binding for storeSubDirectoryLabel follows the same pattern as other conditional UI elements.


263-263: LGTM! Consistent initialization pattern.

The checkbox initialization in refreshView() follows the established pattern and correctly uses true as the default value, matching the task factory behavior.

owlplug-client/src/main/resources/fxml/OptionsView.fxml (4)

10-10: Verify the JavaFX namespace downgrade.

The JavaFX namespace was changed from 17.0.2-ea to 16. Ensure this change is intentional and doesn't introduce compatibility issues.


46-46: LGTM! Width increase accommodates new content.

The VBox width increase from 500.0 to 550.0 provides appropriate space for the new UI elements.


64-67: LGTM! Well-organized General section updates.

The changes improve the UI organization:

  • Reduced spacing (5.0) creates better visual grouping
  • Text clarification: "after application startup" is more accurate than "on application startup"
  • New file statistics checkbox is properly integrated

68-85: LGTM! Clean Explore section reorganization.

The new "Explore" section provides better logical grouping for download-related preferences. The layout structure is well-organized with proper spacing and element arrangement.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests

✅ Copyable Unit Test edits generated.

  • Create PR with unit tests
  • Commit unit tests in branch feat/disable-filestat
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@DropSnorz
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Aug 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Aug 6, 2025

Note

Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link

coderabbitai bot commented Aug 6, 2025

Here are the copyable unit test edits:

Copyable Edits

owlplug-client/src/test/java/com/owlplug/core/components/ApplicationDefaultsTest.java

This is a new file.

/* OwlPlug
 * Copyright (C) 2021 Arthur <[email protected]>
 *
 * This file is part of OwlPlug.
 *
 * OwlPlug is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.
 *
 * OwlPlug is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OwlPlug.  If not, see <https://www.gnu.org/licenses/>.
 */

package com.owlplug.core.components;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import com.owlplug.core.model.OperatingSystem;
import com.owlplug.core.model.RuntimePlatform;
import com.owlplug.explore.model.RemotePackage;
import com.owlplug.plugin.model.PluginFormat;
import com.owlplug.project.model.DawApplication;
import javafx.scene.image.Image;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
import org.springframework.core.env.Environment;

/**
 * Comprehensive unit tests for ApplicationDefaults class.
 * Testing framework: JUnit 5 with Mockito for mocking dependencies.
 */
public class ApplicationDefaultsTest {

    @Mock
    private Environment environment;

    @Mock
    private RuntimePlatformResolver runtimePlatformResolver;

    @Mock
    private RuntimePlatform runtimePlatform;

    @Mock
    private RemotePackage remotePackage;

    @InjectMocks
    private ApplicationDefaults applicationDefaults;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
        when(runtimePlatformResolver.getCurrentPlatform()).thenReturn(runtimePlatform);
    }

    @Test
    public void testApplicationDefaultsCreation() {
        assertNotNull(applicationDefaults);
        assertEquals("OwlPlug", ApplicationDefaults.APPLICATION_NAME);
    }

    @Test
    public void testAllImageConstantsInitialized() {
        // Test all instance image fields
        assertNotNull(applicationDefaults.owlplugLogoSmall);
        assertNotNull(applicationDefaults.directoryImage);
        assertNotNull(applicationDefaults.vst2Image);
        assertNotNull(applicationDefaults.vst3Image);
        assertNotNull(applicationDefaults.auImage);
        assertNotNull(applicationDefaults.lv2Image);
        assertNotNull(applicationDefaults.pluginComponentImage);
        assertNotNull(applicationDefaults.taskPendingImage);
        assertNotNull(applicationDefaults.taskSuccessImage);
        assertNotNull(applicationDefaults.taskFailImage);
        assertNotNull(applicationDefaults.taskRunningImage);
        assertNotNull(applicationDefaults.rocketImage);
        assertNotNull(applicationDefaults.serverImage);
        assertNotNull(applicationDefaults.instrumentImage);
        assertNotNull(applicationDefaults.effectImage);
        assertNotNull(applicationDefaults.tagImage);
        assertNotNull(applicationDefaults.symlinkImage);
        assertNotNull(applicationDefaults.userImage);
        assertNotNull(applicationDefaults.scanDirectoryImage);
        assertNotNull(applicationDefaults.verifiedSourceImage);
        assertNotNull(applicationDefaults.suggestedSourceImage);
        assertNotNull(applicationDefaults.openAudioLogoSmall);
        assertNotNull(applicationDefaults.abletonLogoImage);
        assertNotNull(applicationDefaults.reaperLogoImage);
        assertNotNull(applicationDefaults.errorIconImage);
        assertNotNull(applicationDefaults.linkIconImage);
        assertNotNull(applicationDefaults.pluginPlaceholderImage);
        
        // Test static image field
        assertNotNull(ApplicationDefaults.owlplugLogo);
    }

    @Test
    public void testAllConfigurationKeysHaveCorrectValues() {
        // Plugin directory keys
        assertEquals("VST_DIRECTORY", ApplicationDefaults.VST_DIRECTORY_KEY);
        assertEquals("VST2_DISCOVERY_ENABLED", ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY);
        assertEquals("VST2_EXTRA_DIRECTORY_KEY", ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY);
        assertEquals("VST3_DIRECTORY", ApplicationDefaults.VST3_DIRECTORY_KEY);
        assertEquals("VST3_DISCOVERY_ENABLED", ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY);
        assertEquals("VST3_EXTRA_DIRECTORY_KEY", ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY);
        assertEquals("AU_DIRECTORY_KEY", ApplicationDefaults.AU_DIRECTORY_KEY);
        assertEquals("AU_DISCOVERY_ENABLED_KEY", ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY);
        assertEquals("AU_EXTRA_DIRECTORY_KEY", ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY);
        assertEquals("LV2_DIRECTORY_KEY", ApplicationDefaults.LV2_DIRECTORY_KEY);
        assertEquals("LV2_DISCOVERY_ENABLED_KEY", ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY);
        assertEquals("LV2_EXTRA_DIRECTORY_KEY", ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY);
        
        // Application configuration keys
        assertEquals("NATIVE_HOST_ENABLED_KEY", ApplicationDefaults.NATIVE_HOST_ENABLED_KEY);
        assertEquals("PREFERRED_NATIVE_LOADER", ApplicationDefaults.PREFERRED_NATIVE_LOADER);
        assertEquals("SELECTED_ACCOUNT_KEY", ApplicationDefaults.SELECTED_ACCOUNT_KEY);
        assertEquals("SYNC_PLUGINS_STARTUP_KEY", ApplicationDefaults.SYNC_PLUGINS_STARTUP_KEY);
        assertEquals("STORE_DIRECTORY_ENABLED_KEY", ApplicationDefaults.STORE_DIRECTORY_ENABLED_KEY);
        assertEquals("STORE_BY_CREATOR_ENABLED_KEY", ApplicationDefaults.STORE_BY_CREATOR_ENABLED_KEY);
        assertEquals("STORE_DIRECTORY_KEY", ApplicationDefaults.STORE_DIRECTORY_KEY);
        assertEquals("STORE_SUBDIRECTORY_ENABLED", ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED);
        assertEquals("FIRST_LAUNCH_KEY", ApplicationDefaults.FIRST_LAUNCH_KEY);
        assertEquals("APPLICATION_STATE_KEY", ApplicationDefaults.APPLICATION_STATE_KEY);
        assertEquals("SHOW_DIALOG_DISABLE_PLUGIN_KEY", ApplicationDefaults.SHOW_DIALOG_DISABLE_PLUGIN_KEY);
        assertEquals("PROJECT_DIRECTORY_KEY", ApplicationDefaults.PROJECT_DIRECTORY_KEY);
        assertEquals("PLUGIN_PREFERRED_DISPLAY_KEY", ApplicationDefaults.PLUGIN_PREFERRED_DISPLAY_KEY);
        assertEquals("SYNC_FILE_STAT", ApplicationDefaults.SYNC_FILE_STAT);
    }

    @Test
    public void testGetRuntimePlatform() {
        RuntimePlatform result = applicationDefaults.getRuntimePlatform();
        
        assertEquals(runtimePlatform, result);
        verify(runtimePlatformResolver).getCurrentPlatform();
    }

    @Test
    public void testGetRuntimePlatformWithNullResolver() {
        ApplicationDefaults nullResolverDefaults = new ApplicationDefaults();
        
        assertThrows(NullPointerException.class, nullResolverDefaults::getRuntimePlatform);
    }

    @Test
    public void testGetPluginFormatIconVST2() {
        Image result = applicationDefaults.getPluginFormatIcon(PluginFormat.VST2);
        assertEquals(applicationDefaults.vst2Image, result);
    }

    @Test
    public void testGetPluginFormatIconVST3() {
        Image result = applicationDefaults.getPluginFormatIcon(PluginFormat.VST3);
        assertEquals(applicationDefaults.vst3Image, result);
    }

    @Test
    public void testGetPluginFormatIconAU() {
        Image result = applicationDefaults.getPluginFormatIcon(PluginFormat.AU);
        assertEquals(applicationDefaults.auImage, result);
    }

    @Test
    public void testGetPluginFormatIconLV2() {
        Image result = applicationDefaults.getPluginFormatIcon(PluginFormat.LV2);
        assertEquals(applicationDefaults.lv2Image, result);
    }

    @Test
    public void testGetPluginFormatIconWithNullFormat() {
        Image result = applicationDefaults.getPluginFormatIcon(null);
        assertEquals(applicationDefaults.vst2Image, result);
    }

    @Test
    public void testGetPluginFormatIconConsistentResults() {
        Image result1 = applicationDefaults.getPluginFormatIcon(PluginFormat.VST3);
        Image result2 = applicationDefaults.getPluginFormatIcon(PluginFormat.VST3);
        assertEquals(result1, result2);
    }

    @Test
    public void testGetPackageTypeIconWithNullType() {
        when(remotePackage.getType()).thenReturn(null);
        
        Image result = applicationDefaults.getPackageTypeIcon(remotePackage);
        assertNull(result);
    }

    @Test
    public void testGetPackageTypeIconWithNullPackage() {
        assertThrows(NullPointerException.class, () -> 
            applicationDefaults.getPackageTypeIcon(null));
    }

    @Test
    public void testGetDAWApplicationIconAbleton() {
        Image result = applicationDefaults.getDAWApplicationIcon(DawApplication.ABLETON);
        assertEquals(applicationDefaults.abletonLogoImage, result);
    }

    @Test
    public void testGetDAWApplicationIconReaper() {
        Image result = applicationDefaults.getDAWApplicationIcon(DawApplication.REAPER);
        assertEquals(applicationDefaults.reaperLogoImage, result);
    }

    @Test
    public void testGetDAWApplicationIconConsistentMapping() {
        Image result1 = applicationDefaults.getDAWApplicationIcon(DawApplication.ABLETON);
        Image result2 = applicationDefaults.getDAWApplicationIcon(DawApplication.ABLETON);
        assertEquals(result1, result2);
    }

    @Test
    public void testGetDefaultPluginPathWindowsVST2() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.WIN);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.VST2);
        assertEquals("C:/Program Files/VSTPlugins", result);
    }

    @Test
    public void testGetDefaultPluginPathWindowsVST3() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.WIN);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.VST3);
        assertEquals("C:/Program Files/Common Files/VST3", result);
    }

    @Test
    public void testGetDefaultPluginPathWindowsLV2() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.WIN);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.LV2);
        assertEquals("C:/Program Files/Common Files/LV2", result);
    }

    @Test
    public void testGetDefaultPluginPathWindowsAU() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.WIN);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.AU);
        assertEquals("/path/to/audio/plugins", result);
    }

    @Test
    public void testGetDefaultPluginPathMacVST2() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.MAC);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.VST2);
        assertEquals("/Library/Audio/Plug-ins/VST", result);
    }

    @Test
    public void testGetDefaultPluginPathMacVST3() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.MAC);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.VST3);
        assertEquals("/Library/Audio/Plug-ins/VST3", result);
    }

    @Test
    public void testGetDefaultPluginPathMacAU() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.MAC);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.AU);
        assertEquals("/Library/Audio/Plug-ins/Components", result);
    }

    @Test
    public void testGetDefaultPluginPathMacLV2() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.MAC);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.LV2);
        assertEquals("/Library/Audio/Plug-Ins/LV2", result);
    }

    @Test
    public void testGetDefaultPluginPathLinuxVST2() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.LINUX);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.VST2);
        assertEquals("/usr/lib/vst", result);
    }

    @Test
    public void testGetDefaultPluginPathLinuxVST3() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.LINUX);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.VST3);
        assertEquals("/usr/lib/vst3", result);
    }

    @Test
    public void testGetDefaultPluginPathLinuxLV2() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.LINUX);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.LV2);
        assertEquals("/usr/lib/lv2", result);
    }

    @Test
    public void testGetDefaultPluginPathLinuxAU() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.LINUX);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.AU);
        assertEquals("/path/to/audio/plugins", result);
    }

    @Test
    public void testGetDefaultPluginPathUnknownOS() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(null);
        
        String result = applicationDefaults.getDefaultPluginPath(PluginFormat.VST2);
        assertEquals("/path/to/audio/plugins", result);
    }

    @Test
    public void testGetDefaultPluginPathNullPluginFormat() {
        when(runtimePlatform.getOperatingSystem()).thenReturn(OperatingSystem.WIN);
        
        String result = applicationDefaults.getDefaultPluginPath(null);
        assertEquals("/path/to/audio/plugins", result);
    }

    @Test
    public void testGetVersionFromEnvironment() {
        String expectedVersion = "2.1.3";
        when(environment.getProperty("owlplug.version")).thenReturn(expectedVersion);
        
        String result = applicationDefaults.getVersion();
        assertEquals(expectedVersion, result);
        verify(environment).getProperty("owlplug.version");
    }

    @Test
    public void testGetOwlPlugHubUrlFromEnvironment() {
        String expectedUrl = "https://hub.owlplug.com/api";
        when(environment.getProperty("owlplug.hub.url")).thenReturn(expectedUrl);
        
        String result = applicationDefaults.getOwlPlugHubUrl();
        assertEquals(expectedUrl, result);
        verify(environment).getProperty("owlplug.hub.url");
    }

    @Test
    public void testGetUpdateDownloadUrlFromEnvironment() {
        String expectedUrl = "https://download.owlplug.com/latest";
        when(environment.getProperty("owlplug.hub.updateDownloadUrl")).thenReturn(expectedUrl);
        
        String result = applicationDefaults.getUpdateDownloadUrl();
        assertEquals(expectedUrl, result);
        verify(environment).getProperty("owlplug.hub.updateDownloadUrl");
    }

    @Test
    public void testGetOwlPlugRegistryUrlFromEnvironment() {
        String expectedUrl = "https://registry.owlplug.com/v1";
        when(environment.getProperty("owlplug.registry.url")).thenReturn(expectedUrl);
        
        String result = applicationDefaults.getOwlPlugRegistryUrl();
        assertEquals(expectedUrl, result);
        verify(environment).getProperty("owlplug.registry.url");
    }

    @Test
    public void testGetOpenAudioRegistryUrlFromEnvironment() {
        String expectedUrl = "https://registry.openaudio.com/api";
        when(environment.getProperty("openaudio.registry.url")).thenReturn(expectedUrl);
        
        String result = applicationDefaults.getOpenAudioRegistryUrl();
        assertEquals(expectedUrl, result);
        verify(environment).getProperty("openaudio.registry.url");
    }

    @Test
    public void testGetEnvPropertyCustomProperty() {
        String propertyKey = "custom.test.property";
        String expectedValue = "custom-test-value";
        when(environment.getProperty(propertyKey)).thenReturn(expectedValue);
        
        String result = applicationDefaults.getEnvProperty(propertyKey);
        assertEquals(expectedValue, result);
        verify(environment).getProperty(propertyKey);
    }

    @Test
    public void testGetEnvPropertyNonExistentProperty() {
        String propertyKey = "non.existent.property";
        when(environment.getProperty(propertyKey)).thenReturn(null);
        
        String result = applicationDefaults.getEnvProperty(propertyKey);
        assertNull(result);
        verify(environment).getProperty(propertyKey);
    }

    @Test
    public void testGetEnvPropertyWithEmptyString() {
        when(environment.getProperty("test.empty")).thenReturn("");
        String result = applicationDefaults.getEnvProperty("test.empty");
        assertEquals("", result);
    }

    @Test
    public void testGetContributorsLoadsFromResource() {
        String contributorsContent = "Alice Developer\nBob Contributor\nCharlie Tester";
        InputStream mockInputStream = new ByteArrayInputStream(contributorsContent.getBytes());
        
        try (MockedStatic<ApplicationDefaults> mockedStatic = mockStatic(ApplicationDefaults.class)) {
            mockedStatic.when(() -> ApplicationDefaults.class.getResourceAsStream("/included/CONTRIBUTORS"))
                      .thenReturn(mockInputStream);
            mockedStatic.when(ApplicationDefaults::getContributors).thenCallRealMethod();
            
            List<String> result = ApplicationDefaults.getContributors();
            
            assertEquals(3, result.size());
            assertTrue(result.contains("Alice Developer"));
            assertTrue(result.contains("Bob Contributor"));
            assertTrue(result.contains("Charlie Tester"));
        }
    }

    @Test
    public void testGetContributorsFiltersEmptyLines() {
        String contributorsContent = "Alice\n\nBob\n   \n\t\nCharlie\n ";
        InputStream mockInputStream = new ByteArrayInputStream(contributorsContent.getBytes());
        
        try (MockedStatic<ApplicationDefaults> mockedStatic = mockStatic(ApplicationDefaults.class)) {
            mockedStatic.when(() -> ApplicationDefaults.class.getResourceAsStream("/included/CONTRIBUTORS"))
                      .thenReturn(mockInputStream);
            mockedStatic.when(ApplicationDefaults::getContributors).thenCallRealMethod();
            
            List<String> result = ApplicationDefaults.getContributors();
            
            assertEquals(3, result.size());
            assertTrue(result.contains("Alice"));
            assertTrue(result.contains("Bob"));
            assertTrue(result.contains("Charlie"));
        }
    }

    @Test
    public void testGetContributorsReturnsNewInstanceEachCall() {
        String contributorsContent = "Alice\nBob";
        InputStream mockInputStream1 = new ByteArrayInputStream(contributorsContent.getBytes());
        InputStream mockInputStream2 = new ByteArrayInputStream(contributorsContent.getBytes());
        
        try (MockedStatic<ApplicationDefaults> mockedStatic = mockStatic(ApplicationDefaults.class)) {
            mockedStatic.when(() -> ApplicationDefaults.class.getResourceAsStream("/included/CONTRIBUTORS"))
                      .thenReturn(mockInputStream1).thenReturn(mockInputStream2);
            mockedStatic.when(ApplicationDefaults::getContributors).thenCallRealMethod();
            
            List<String> result1 = ApplicationDefaults.getContributors();
            List<String> result2 = ApplicationDefaults.getContributors();
            
            assertNotSame(result1, result2);
            assertEquals(result1, result2);
            
            // Modifying one shouldn't affect the other
            result1.add("Dave");
            assertNotEquals(result1.size(), result2.size());
        }
    }

    @Test
    public void testGetContributorsThrowsRuntimeExceptionWhenResourceNotFound() {
        try (MockedStatic<ApplicationDefaults> mockedStatic = mockStatic(ApplicationDefaults.class)) {
            mockedStatic.when(() -> ApplicationDefaults.class.getResourceAsStream("/included/CONTRIBUTORS"))
                      .thenReturn(null);
            mockedStatic.when(ApplicationDefaults::getContributors).thenCallRealMethod();
            
            RuntimeException exception = assertThrows(RuntimeException.class, 
                ApplicationDefaults::getContributors);
            
            assertTrue(exception.getMessage().contains("Resource not found: /included/CONTRIBUTORS"));
        }
    }

    @Test
    public void testGetUserDataDirectory() {
        String result = ApplicationDefaults.getUserDataDirectory();
        
        assertNotNull(result);
        assertTrue(result.endsWith(".owlplug"));
        assertTrue(result.contains(System.getProperty("user.home")));
    }

    @Test
    public void testGetTempDownloadDirectory() {
        String result = ApplicationDefaults.getTempDownloadDirectory();
        String userDataDir = ApplicationDefaults.getUserDataDirectory();
        
        assertNotNull(result);
        assertTrue(result.startsWith(userDataDir));
        assertTrue(result.endsWith("temp"));
    }

    @Test
    public void testGetLogDirectory() {
        String result = ApplicationDefaults.getLogDirectory();
        String userDataDir = ApplicationDefaults.getUserDataDirectory();
        
        assertNotNull(result);
        assertTrue(result.startsWith(userDataDir));
        assertTrue(result.endsWith("logs"));
    }

    @Test
    public void testDirectoryConsistency() {
        String userDataDir = ApplicationDefaults.getUserDataDirectory();
        String tempDir = ApplicationDefaults.getTempDownloadDirectory();
        String logDir = ApplicationDefaults.getLogDirectory();
        
        assertTrue(tempDir.startsWith(userDataDir));
        assertTrue(logDir.startsWith(userDataDir));
        assertNotEquals(tempDir, logDir);
    }

    @Test
    public void testDirectoryPathsWithCustomUserHome() {
        String originalHome = System.getProperty("user.home");
        String customHome = "/custom/home";
        
        try {
            System.setProperty("user.home", customHome);
            String result = ApplicationDefaults.getUserDataDirectory();
            assertTrue(result.startsWith(customHome));
            assertTrue(result.endsWith(".owlplug"));
        } finally {
            System.setProperty("user.home", originalHome);
        }
    }

    @Test
    public void testConsistentPathsAcrossMultipleCalls() {
        String userDir1 = ApplicationDefaults.getUserDataDirectory();
        String userDir2 = ApplicationDefaults.getUserDataDirectory();
        String tempDir1 = ApplicationDefaults.getTempDownloadDirectory();
        String tempDir2 = ApplicationDefaults.getTempDownloadDirectory();
        String logDir1 = ApplicationDefaults.getLogDirectory();
        String logDir2 = ApplicationDefaults.getLogDirectory();
        
        assertEquals(userDir1, userDir2);
        assertEquals(tempDir1, tempDir2);
        assertEquals(logDir1, logDir2);
    }

    @Test
    public void testConcurrentAccessToStaticMethods() throws InterruptedException {
        int threadCount = 10;
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            executorService.submit(() -> {
                try {
                    String userDir = ApplicationDefaults.getUserDataDirectory();
                    String tempDir = ApplicationDefaults.getTempDownloadDirectory();
                    String logDir = ApplicationDefaults.getLogDirectory();
                    
                    assertNotNull(userDir);
                    assertNotNull(tempDir);
                    assertNotNull(logDir);
                    assertTrue(tempDir.startsWith(userDir));
                    assertTrue(logDir.startsWith(userDir));
                } finally {
                    latch.countDown();
                }
            });
        }
        
        assertTrue(latch.await(5, TimeUnit.SECONDS));
        executorService.shutdown();
    }

    @Test
    public void testMultipleCallsToImageGettersConsistent() {
        for (int i = 0; i < 10; i++) {
            Image vst3Icon = applicationDefaults.getPluginFormatIcon(PluginFormat.VST3);
            Image abletonIcon = applicationDefaults.getDAWApplicationIcon(DawApplication.ABLETON);
            
            assertEquals(applicationDefaults.vst3Image, vst3Icon);
            assertEquals(applicationDefaults.abletonLogoImage, abletonIcon);
        }
    }

    @Test
    public void testImageConstantsObjectIdentity() {
        // Verify that image constants are the same object each time
        Image logo1 = applicationDefaults.owlplugLogoSmall;
        Image logo2 = applicationDefaults.owlplugLogoSmall;
        assertSame(logo1, logo2);
        
        Image staticLogo1 = ApplicationDefaults.owlplugLogo;
        Image staticLogo2 = ApplicationDefaults.owlplugLogo;
        assertSame(staticLogo1, staticLogo2);
    }

    @Test
    public void testSystemPropertyModificationsHandled() {
        String originalHome = System.getProperty("user.home");
        
        try {
            // Test with empty user.home
            System.setProperty("user.home", "");
            assertDoesNotThrow(() -> ApplicationDefaults.getUserDataDirectory());
            
            // Test with special characters
            System.setProperty("user.home", "/path with spaces/and-special@chars");
            String result = ApplicationDefaults.getUserDataDirectory();
            assertNotNull(result);
            assertTrue(result.contains("and-special@chars"));
            
        } finally {
            if (originalHome != null) {
                System.setProperty("user.home", originalHome);
            }
        }
    }

    @Test
    public void testEnvironmentPropertyEdgeCases() {
        // Test with very long string
        String longValue = "x".repeat(1000);
        when(environment.getProperty("test.long")).thenReturn(longValue);
        assertEquals(longValue, applicationDefaults.getEnvProperty("test.long"));
        
        // Test with special characters
        String specialValue = "Special!@#$%^&*()_+{}|:<>?[];',./`~";
        when(environment.getProperty("test.special")).thenReturn(specialValue);
        assertEquals(specialValue, applicationDefaults.getEnvProperty("test.special"));
    }

    @Test
    public void testAllStaticConstantsAreNonNull() {
        assertNotNull(ApplicationDefaults.APPLICATION_NAME);
        assertNotNull(ApplicationDefaults.VST_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY);
        assertNotNull(ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.VST3_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY);
        assertNotNull(ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.AU_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY);
        assertNotNull(ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.LV2_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY);
        assertNotNull(ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.NATIVE_HOST_ENABLED_KEY);
        assertNotNull(ApplicationDefaults.PREFERRED_NATIVE_LOADER);
        assertNotNull(ApplicationDefaults.SELECTED_ACCOUNT_KEY);
        assertNotNull(ApplicationDefaults.SYNC_PLUGINS_STARTUP_KEY);
        assertNotNull(ApplicationDefaults.STORE_DIRECTORY_ENABLED_KEY);
        assertNotNull(ApplicationDefaults.STORE_BY_CREATOR_ENABLED_KEY);
        assertNotNull(ApplicationDefaults.STORE_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.STORE_SUBDIRECTORY_ENABLED);
        assertNotNull(ApplicationDefaults.FIRST_LAUNCH_KEY);
        assertNotNull(ApplicationDefaults.APPLICATION_STATE_KEY);
        assertNotNull(ApplicationDefaults.SHOW_DIALOG_DISABLE_PLUGIN_KEY);
        assertNotNull(ApplicationDefaults.PROJECT_DIRECTORY_KEY);
        assertNotNull(ApplicationDefaults.PLUGIN_PREFERRED_DISPLAY_KEY);
        assertNotNull(ApplicationDefaults.SYNC_FILE_STAT);
    }

    @Test
    public void testGetDefaultPluginPathForAllFormatsOnAllPlatforms() {
        OperatingSystem[] operatingSystems = {OperatingSystem.WIN, OperatingSystem.MAC, OperatingSystem.LINUX, OperatingSystem.UNKNOWN};
        PluginFormat[] pluginFormats = {PluginFormat.VST2, PluginFormat.VST3, PluginFormat.AU, PluginFormat.LV2};
        
        for (OperatingSystem os : operatingSystems) {
            for (PluginFormat format : pluginFormats) {
                when(runtimePlatform.getOperatingSystem()).thenReturn(os);
                String result = applicationDefaults.getDefaultPluginPath(format);
                assertNotNull(result);
                assertFalse(result.isEmpty());
            }
        }
    }

    @Test
    public void testGetPluginFormatIconForAllFormats() {
        PluginFormat[] pluginFormats = {PluginFormat.VST2, PluginFormat.VST3, PluginFormat.AU, PluginFormat.LV2};
        
        for (PluginFormat format : pluginFormats) {
            Image result = applicationDefaults.getPluginFormatIcon(format);
            assertNotNull(result);
        }
    }

    @Test
    public void testGetDAWApplicationIconForAllApplications() {
        DawApplication[] dawApplications = {DawApplication.ABLETON, DawApplication.REAPER};
        
        for (DawApplication application : dawApplications) {
            Image result = applicationDefaults.getDAWApplicationIcon(application);
            assertNotNull(result);
        }
    }

    @Test
    public void testRuntimePlatformResolverCallsNotCached() {
        // Call multiple times
        applicationDefaults.getRuntimePlatform();
        applicationDefaults.getRuntimePlatform();
        applicationDefaults.getRuntimePlatform();
        
        // Should delegate to resolver each time (no caching in ApplicationDefaults)
        verify(runtimePlatformResolver, times(3)).getCurrentPlatform();
    }
}

owlplug-client/src/test/java/com/owlplug/plugin/components/PluginTaskFactoryTest.java

This is a new file.

/* OwlPlug
 * Copyright (C) 2021 Arthur <[email protected]>
 *
 * This file is part of OwlPlug.
 *
 * OwlPlug is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.
 *
 * OwlPlug is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OwlPlug.  If not, see <https://www.gnu.org/licenses/>.
 */

package com.owlplug.plugin.components;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import com.owlplug.core.components.ApplicationDefaults;
import com.owlplug.core.components.ApplicationPreferences;
import com.owlplug.core.model.platform.RuntimePlatform;
import com.owlplug.core.tasks.SimpleEventListener;
import com.owlplug.core.tasks.TaskExecutionContext;
import com.owlplug.plugin.model.Plugin;
import com.owlplug.plugin.repositories.FileStatRepository;
import com.owlplug.plugin.repositories.PluginFootprintRepository;
import com.owlplug.plugin.repositories.PluginRepository;
import com.owlplug.plugin.repositories.SymlinkRepository;
import com.owlplug.plugin.services.NativeHostService;
import com.owlplug.plugin.services.PluginService;
import com.owlplug.project.components.ProjectTaskFactory;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class PluginTaskFactoryTest {

  @Mock
  private ApplicationDefaults applicationDefaults;

  @Mock
  private ApplicationPreferences prefs;

  @Mock
  private PluginRepository pluginRepository;

  @Mock
  private PluginService pluginService;

  @Mock
  private PluginFootprintRepository pluginFootprintRepository;

  @Mock
  private SymlinkRepository symlinkRepository;

  @Mock
  private NativeHostService nativeHostService;

  @Mock
  private ProjectTaskFactory projectTaskFactory;

  @Mock
  private FileStatRepository fileStatRepository;

  @InjectMocks
  private PluginTaskFactory pluginTaskFactory;

  private Plugin testPlugin;

  @BeforeEach
  public void setUp() {
    testPlugin = new Plugin();
    testPlugin.setName("Test Plugin");
    testPlugin.setPath("/test/path");
  }

  @Test
  public void testCreatePluginSyncTask_WithoutDirectoryScope_ShouldCreateTaskWithDefaultParameters() {
    // Arrange
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.WIN_64);
    when(prefs.get(ApplicationDefaults.VST_DIRECTORY_KEY, "")).thenReturn("/vst");
    when(prefs.get(ApplicationDefaults.VST3_DIRECTORY_KEY, "")).thenReturn("/vst3");
    when(prefs.get(ApplicationDefaults.AU_DIRECTORY_KEY, "")).thenReturn("/au");
    when(prefs.get(ApplicationDefaults.LV2_DIRECTORY_KEY, "")).thenReturn("/lv2");
    when(prefs.getBoolean(ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, false)).thenReturn(true);
    when(prefs.getBoolean(ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, false)).thenReturn(true);
    when(prefs.getBoolean(ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, false)).thenReturn(false);
    when(prefs.getBoolean(ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, false)).thenReturn(false);
    when(prefs.getList(ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY)).thenReturn(Arrays.asList("/extra1"));
    when(prefs.getList(ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY)).thenReturn(Arrays.asList("/extra2"));
    when(prefs.getList(ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY)).thenReturn(Arrays.asList());
    when(prefs.getList(ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY)).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask();

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
    
    // Verify all necessary preferences were queried
    verify(prefs).get(ApplicationDefaults.VST_DIRECTORY_KEY, "");
    verify(prefs).get(ApplicationDefaults.VST3_DIRECTORY_KEY, "");
    verify(prefs).get(ApplicationDefaults.AU_DIRECTORY_KEY, "");
    verify(prefs).get(ApplicationDefaults.LV2_DIRECTORY_KEY, "");
    verify(prefs).getBoolean(ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, false);
    verify(prefs).getBoolean(ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, false);
    verify(prefs).getBoolean(ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, false);
    verify(prefs).getBoolean(ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, false);
  }

  @Test
  public void testCreatePluginSyncTask_WithDirectoryScope_ShouldCreateTaskWithScopedParameters() {
    // Arrange
    String directoryScope = "/test/directory";
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.MAC);
    when(prefs.get(ApplicationDefaults.VST_DIRECTORY_KEY, "")).thenReturn("/vst");
    when(prefs.get(ApplicationDefaults.VST3_DIRECTORY_KEY, "")).thenReturn("/vst3");
    when(prefs.get(ApplicationDefaults.AU_DIRECTORY_KEY, "")).thenReturn("/au");
    when(prefs.get(ApplicationDefaults.LV2_DIRECTORY_KEY, "")).thenReturn("/lv2");
    when(prefs.getBoolean(ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, false)).thenReturn(false);
    when(prefs.getBoolean(ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, false)).thenReturn(true);
    when(prefs.getBoolean(ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, false)).thenReturn(true);
    when(prefs.getBoolean(ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, false)).thenReturn(false);
    when(prefs.getList(ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY)).thenReturn(Arrays.asList());
    when(prefs.getList(ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY)).thenReturn(Arrays.asList());
    when(prefs.getList(ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY)).thenReturn(Arrays.asList("/au/extra"));
    when(prefs.getList(ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY)).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask(directoryScope);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testCreatePluginSyncTask_WithNullDirectoryScope_ShouldCreateTaskWithoutScope() {
    // Arrange
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.LINUX_64);
    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(true);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask(null);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testCreatePluginSyncTask_WithEmptyDirectoryScope_ShouldCreateTaskWithEmptyScope() {
    // Arrange
    String emptyDirectoryScope = "";
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.LINUX_64);
    when(prefs.get(anyString(), anyString())).thenReturn("/default");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(false);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList("/extra1", "/extra2"));

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask(emptyDirectoryScope);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testCreatePluginSyncTask_WithAllDiscoveryEnabled_ShouldCreateTaskWithAllFormats() {
    // Arrange
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.WIN_64);
    when(prefs.get(ApplicationDefaults.VST_DIRECTORY_KEY, "")).thenReturn("/vst");
    when(prefs.get(ApplicationDefaults.VST3_DIRECTORY_KEY, "")).thenReturn("/vst3");
    when(prefs.get(ApplicationDefaults.AU_DIRECTORY_KEY, "")).thenReturn("/au");
    when(prefs.get(ApplicationDefaults.LV2_DIRECTORY_KEY, "")).thenReturn("/lv2");
    when(prefs.getBoolean(ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, false)).thenReturn(true);
    when(prefs.getBoolean(ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, false)).thenReturn(true);
    when(prefs.getBoolean(ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, false)).thenReturn(true);
    when(prefs.getBoolean(ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, false)).thenReturn(true);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask();

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
    
    // Verify all necessary preferences were queried
    verify(prefs).getBoolean(ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, false);
    verify(prefs).getBoolean(ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, false);
    verify(prefs).getBoolean(ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, false);
    verify(prefs).getBoolean(ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, false);
  }

  @Test
  public void testCreatePluginSyncTask_WithAllDiscoveryDisabled_ShouldCreateTaskWithNoFormats() {
    // Arrange
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.MAC);
    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, false)).thenReturn(false);
    when(prefs.getBoolean(ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, false)).thenReturn(false);
    when(prefs.getBoolean(ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, false)).thenReturn(false);
    when(prefs.getBoolean(ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, false)).thenReturn(false);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask();

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testCreateFileStatSyncTask_WithoutDirectoryScope_ShouldCreateTaskWithAllDirectories() {
    // Arrange
    Set<String> testDirectories = new HashSet<>(Arrays.asList("/dir1", "/dir2", "/dir3"));
    when(pluginService.getDirectoriesExplorationSet()).thenReturn(testDirectories);

    // Act
    TaskExecutionContext result = pluginTaskFactory.createFileStatSyncTask();

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
    verify(pluginService).getDirectoriesExplorationSet();
  }

  @Test
  public void testCreateFileStatSyncTask_WithEmptyDirectorySet_ShouldCreateTaskWithEmptyList() {
    // Arrange
    Set<String> emptyDirectories = new HashSet<>();
    when(pluginService.getDirectoriesExplorationSet()).thenReturn(emptyDirectories);

    // Act
    TaskExecutionContext result = pluginTaskFactory.createFileStatSyncTask();

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
    verify(pluginService).getDirectoriesExplorationSet();
  }

  @Test
  public void testCreateFileStatSyncTask_WithDirectoryScope_ShouldCreateTaskWithSpecificDirectory() {
    // Arrange
    String directoryScope = "/specific/directory";

    // Act
    TaskExecutionContext result = pluginTaskFactory.createFileStatSyncTask(directoryScope);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
    
    // Verify pluginService is not called when directory scope is provided
    verify(pluginService, never()).getDirectoriesExplorationSet();
  }

  @Test
  public void testCreateFileStatSyncTask_WithNullDirectoryScope_ShouldCreateTaskWithNullScope() {
    // Act
    TaskExecutionContext result = pluginTaskFactory.createFileStatSyncTask(null);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
    verify(pluginService, never()).getDirectoriesExplorationSet();
  }

  @Test
  public void testCreateFileStatSyncTask_WithEmptyStringDirectoryScope_ShouldCreateTaskWithEmptyScope() {
    // Act
    TaskExecutionContext result = pluginTaskFactory.createFileStatSyncTask("");

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
    verify(pluginService, never()).getDirectoriesExplorationSet();
  }

  @Test
  public void testCreatePluginRemoveTask_WithValidPlugin_ShouldCreateTask() {
    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginRemoveTask(testPlugin);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testCreatePluginRemoveTask_WithNullPlugin_ShouldStillCreateTask() {
    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginRemoveTask(null);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testAddSyncPluginsListener_WithValidListener_ShouldAddToList() {
    // Arrange
    SimpleEventListener listener = mock(SimpleEventListener.class);
    
    // Act
    pluginTaskFactory.addSyncPluginsListener(listener);
    
    // Assert - No direct assertion possible due to private field, but verify no exception thrown
    assertDoesNotThrow(() -> pluginTaskFactory.addSyncPluginsListener(listener));
  }

  @Test
  public void testAddSyncPluginsListener_WithNullListener_ShouldNotThrow() {
    // Act & Assert
    assertDoesNotThrow(() -> pluginTaskFactory.addSyncPluginsListener(null));
  }

  @Test
  public void testAddSyncPluginsListener_WithMultipleListeners_ShouldAddAll() {
    // Arrange
    SimpleEventListener listener1 = mock(SimpleEventListener.class);
    SimpleEventListener listener2 = mock(SimpleEventListener.class);
    SimpleEventListener listener3 = mock(SimpleEventListener.class);
    
    // Act & Assert - Verify multiple additions work without exception
    assertDoesNotThrow(() -> {
      pluginTaskFactory.addSyncPluginsListener(listener1);
      pluginTaskFactory.addSyncPluginsListener(listener2);
      pluginTaskFactory.addSyncPluginsListener(listener3);
    });
  }

  @Test
  public void testRemoveSyncPluginsListener_WithExistingListener_ShouldRemove() {
    // Arrange
    SimpleEventListener listener = mock(SimpleEventListener.class);
    pluginTaskFactory.addSyncPluginsListener(listener);
    
    // Act & Assert
    assertDoesNotThrow(() -> pluginTaskFactory.removeSyncPluginsListener(listener));
  }

  @Test
  public void testRemoveSyncPluginsListener_WithNonExistingListener_ShouldNotThrow() {
    // Arrange
    SimpleEventListener listener = mock(SimpleEventListener.class);
    
    // Act & Assert
    assertDoesNotThrow(() -> pluginTaskFactory.removeSyncPluginsListener(listener));
  }

  @Test
  public void testRemoveSyncPluginsListener_WithNullListener_ShouldNotThrow() {
    // Act & Assert
    assertDoesNotThrow(() -> pluginTaskFactory.removeSyncPluginsListener(null));
  }

  @Test
  public void testCreatePluginSyncTask_WithExtraDirectories_ShouldIncludeExtraDirectories() {
    // Arrange
    List<String> vst2Extras = Arrays.asList("/extra/vst2/1", "/extra/vst2/2");
    List<String> vst3Extras = Arrays.asList("/extra/vst3/1");
    List<String> auExtras = Arrays.asList("/extra/au/1", "/extra/au/2", "/extra/au/3");
    List<String> lv2Extras = Arrays.asList("/extra/lv2/1");

    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.LINUX_64);
    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(true);
    when(prefs.getList(ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY)).thenReturn(vst2Extras);
    when(prefs.getList(ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY)).thenReturn(vst3Extras);
    when(prefs.getList(ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY)).thenReturn(auExtras);
    when(prefs.getList(ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY)).thenReturn(lv2Extras);

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask();

    // Assert
    assertNotNull(result);
    verify(prefs).getList(ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY);
    verify(prefs).getList(ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY);
    verify(prefs).getList(ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY);
    verify(prefs).getList(ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY);
  }

  @Test
  public void testCreatePluginSyncTask_WithLongDirectoryPath_ShouldHandleLongPaths() {
    // Arrange
    String longDirectory = "/very/long/directory/path/that/might/cause/issues/in/some/systems/" +
                          "with/many/subdirectories/and/a/very/long/name/that/exceeds/normal/limits";
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.WIN_64);
    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(false);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask(longDirectory);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testCreatePluginSyncTask_WithSpecialCharactersInDirectory_ShouldHandleSpecialChars() {
    // Arrange
    String specialDirectory = "/directory with spaces/and-dashes/and_underscores/[brackets]/äöü";
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.MAC);
    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(false);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask(specialDirectory);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testCreatePluginSyncTask_WithDifferentPlatforms_ShouldCreateTaskForAllPlatforms() {
    // Arrange
    RuntimePlatform[] platforms = {
      RuntimePlatform.WIN_64,
      RuntimePlatform.MAC,
      RuntimePlatform.LINUX_64
    };

    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(false);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());

    for (RuntimePlatform platform : platforms) {
      // Arrange
      when(applicationDefaults.getRuntimePlatform()).thenReturn(platform);

      // Act
      TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask();

      // Assert
      assertNotNull(result, "Task should be created for platform: " + platform);
      assertNotNull(result.getTask(), "Task should not be null for platform: " + platform);
    }
  }

  @Test
  public void testCreatePluginSyncTask_CallsOverloadedMethod_ShouldCreateTask() {
    // Arrange
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.WIN_64);
    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(false);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result1 = pluginTaskFactory.createPluginSyncTask();
    TaskExecutionContext result2 = pluginTaskFactory.createPluginSyncTask(null);

    // Assert
    assertNotNull(result1);
    assertNotNull(result2);
    assertEquals(result1.getClass(), result2.getClass());
  }

  @Test
  public void testCreatePluginSyncTask_WithMaximumExtraDirectories_ShouldHandleLargeLists() {
    // Arrange
    List<String> largeExtraList = Arrays.asList(
        "/extra1", "/extra2", "/extra3", "/extra4", "/extra5",
        "/extra6", "/extra7", "/extra8", "/extra9", "/extra10"
    );
    
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.LINUX_64);
    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(true);
    when(prefs.getList(anyString())).thenReturn(largeExtraList);

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask();

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testPluginTaskFactory_AllPublicMethods_ShouldNotReturnNull() {
    // Arrange
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.WIN_64);
    when(prefs.get(anyString(), anyString())).thenReturn("");
    when(prefs.getBoolean(anyString(), anyBoolean())).thenReturn(false);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());
    when(pluginService.getDirectoriesExplorationSet()).thenReturn(new HashSet<>());

    // Act & Assert
    assertNotNull(pluginTaskFactory.createPluginSyncTask());
    assertNotNull(pluginTaskFactory.createPluginSyncTask(null));
    assertNotNull(pluginTaskFactory.createPluginSyncTask("/test"));
    assertNotNull(pluginTaskFactory.createFileStatSyncTask());
    assertNotNull(pluginTaskFactory.createFileStatSyncTask("/test"));
    assertNotNull(pluginTaskFactory.createPluginRemoveTask(testPlugin));
    assertNotNull(pluginTaskFactory.createPluginRemoveTask(null));
  }

  @Test
  public void testListenerManagement_AddAndRemoveSameListener_ShouldNotThrow() {
    // Arrange
    SimpleEventListener listener = mock(SimpleEventListener.class);
    
    // Act & Assert
    assertDoesNotThrow(() -> {
      pluginTaskFactory.addSyncPluginsListener(listener);
      pluginTaskFactory.addSyncPluginsListener(listener); // Add same listener twice
      pluginTaskFactory.removeSyncPluginsListener(listener);
      pluginTaskFactory.removeSyncPluginsListener(listener); // Remove same listener twice
    });
  }

  @Test
  public void testCreatePluginSyncTask_WithEmptyPreferences_ShouldUseDefaults() {
    // Arrange
    when(applicationDefaults.getRuntimePlatform()).thenReturn(RuntimePlatform.WIN_64);
    when(prefs.get(anyString(), eq(""))).thenReturn("");
    when(prefs.getBoolean(anyString(), eq(false))).thenReturn(false);
    when(prefs.getList(anyString())).thenReturn(Arrays.asList());

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginSyncTask();

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

  @Test
  public void testCreateFileStatSyncTask_WithLargeDirectorySet_ShouldHandleLargeSet() {
    // Arrange
    Set<String> largeDirectorySet = new HashSet<>();
    for (int i = 0; i < 100; i++) {
      largeDirectorySet.add("/directory" + i);
    }
    when(pluginService.getDirectoriesExplorationSet()).thenReturn(largeDirectorySet);

    // Act
    TaskExecutionContext result = pluginTaskFactory.createFileStatSyncTask();

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
    verify(pluginService).getDirectoriesExplorationSet();
  }

  @Test
  public void testCreatePluginRemoveTask_WithPluginHavingSpecialCharacters_ShouldCreateTask() {
    // Arrange
    Plugin specialPlugin = new Plugin();
    specialPlugin.setName("Plugin with äöü & special-chars");
    specialPlugin.setPath("/path/with spaces/and-special_chars");

    // Act
    TaskExecutionContext result = pluginTaskFactory.createPluginRemoveTask(specialPlugin);

    // Assert
    assertNotNull(result);
    assertNotNull(result.getTask());
  }

}

@DropSnorz DropSnorz merged commit f8cc50b into master Aug 6, 2025
3 checks passed
@DropSnorz DropSnorz moved this to Done in OwlPlug 1.30 Aug 11, 2025
@coderabbitai coderabbitai bot mentioned this pull request Aug 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants