diff --git a/inc/Abilities/WorkspaceAbilities.php b/inc/Abilities/WorkspaceAbilities.php index fca1db26..1022166f 100644 --- a/inc/Abilities/WorkspaceAbilities.php +++ b/inc/Abilities/WorkspaceAbilities.php @@ -2559,9 +2559,11 @@ public static function showRepo( array $input ): array|\WP_Error { * @return array Result. */ public static function readFile( array $input ): array|\WP_Error { - $input = self::normalize_mounted_workspace_path_input($input, array( 'repo' )); - if ( RemoteWorkspaceBackend::should_handle() ) { - return ( new RemoteWorkspaceBackend() )->read_file( + $input = self::normalize_mounted_workspace_path_input($input, array( 'repo' )); + $workspace = new Workspace(); + $reader = new WorkspaceReader($workspace); + if ( RemoteWorkspaceBackend::should_handle() && null !== self::showLocalWorkspaceHandleIfPresent($workspace, (string) ( $input['repo'] ?? '' )) ) { + return $reader->read_file( $input['repo'] ?? '', $input['path'] ?? '', isset($input['max_size']) ? (int) $input['max_size'] : Workspace::MAX_READ_SIZE, @@ -2570,8 +2572,18 @@ public static function readFile( array $input ): array|\WP_Error { ); } - $workspace = new Workspace(); - $reader = new WorkspaceReader($workspace); + if ( RemoteWorkspaceBackend::should_handle() ) { + $result = ( new RemoteWorkspaceBackend() )->read_file( + $input['repo'] ?? '', + $input['path'] ?? '', + isset($input['max_size']) ? (int) $input['max_size'] : Workspace::MAX_READ_SIZE, + isset($input['offset']) ? (int) $input['offset'] : null, + isset($input['limit']) ? (int) $input['limit'] : null + ); + if ( ! self::shouldFallbackToLocalWorkspace($result) ) { + return $result; + } + } return $reader->read_file( $input['repo'] ?? '', @@ -2589,16 +2601,25 @@ public static function readFile( array $input ): array|\WP_Error { * @return array Result. */ public static function listDirectory( array $input ): array|\WP_Error { - $input = self::normalize_mounted_workspace_path_input($input, array( 'repo' )); - if ( RemoteWorkspaceBackend::should_handle() ) { - return ( new RemoteWorkspaceBackend() )->list_directory( + $input = self::normalize_mounted_workspace_path_input($input, array( 'repo' )); + $workspace = new Workspace(); + $reader = new WorkspaceReader($workspace); + if ( RemoteWorkspaceBackend::should_handle() && null !== self::showLocalWorkspaceHandleIfPresent($workspace, (string) ( $input['repo'] ?? '' )) ) { + return $reader->list_directory( $input['repo'] ?? '', $input['path'] ?? null ); } - $workspace = new Workspace(); - $reader = new WorkspaceReader($workspace); + if ( RemoteWorkspaceBackend::should_handle() ) { + $result = ( new RemoteWorkspaceBackend() )->list_directory( + $input['repo'] ?? '', + $input['path'] ?? null + ); + if ( ! self::shouldFallbackToLocalWorkspace($result) ) { + return $result; + } + } return $reader->list_directory( $input['repo'] ?? '', @@ -2613,9 +2634,11 @@ public static function listDirectory( array $input ): array|\WP_Error { * @return array Result. */ public static function grepFiles( array $input ): array|\WP_Error { - $input = self::normalize_mounted_workspace_path_input($input, array( 'repo' )); - if ( RemoteWorkspaceBackend::should_handle() ) { - return ( new RemoteWorkspaceBackend() )->grep( + $input = self::normalize_mounted_workspace_path_input($input, array( 'repo' )); + $workspace = new Workspace(); + $reader = new WorkspaceReader($workspace); + if ( RemoteWorkspaceBackend::should_handle() && null !== self::showLocalWorkspaceHandleIfPresent($workspace, (string) ( $input['repo'] ?? '' )) ) { + return $reader->grep( $input['repo'] ?? '', $input['pattern'] ?? '', $input['path'] ?? null, @@ -2625,8 +2648,19 @@ public static function grepFiles( array $input ): array|\WP_Error { ); } - $workspace = new Workspace(); - $reader = new WorkspaceReader($workspace); + if ( RemoteWorkspaceBackend::should_handle() ) { + $result = ( new RemoteWorkspaceBackend() )->grep( + $input['repo'] ?? '', + $input['pattern'] ?? '', + $input['path'] ?? null, + $input['include'] ?? null, + isset($input['max_results']) ? (int) $input['max_results'] : 100, + isset($input['context_lines']) ? (int) $input['context_lines'] : 0 + ); + if ( ! self::shouldFallbackToLocalWorkspace($result) ) { + return $result; + } + } return $reader->grep( $input['repo'] ?? '', diff --git a/tests/smoke-workspace-grep.php b/tests/smoke-workspace-grep.php index 92665b12..4b96033f 100644 --- a/tests/smoke-workspace-grep.php +++ b/tests/smoke-workspace-grep.php @@ -53,6 +53,7 @@ function size_format( $bytes ): string } include __DIR__ . '/../inc/Support/PathSecurity.php'; + include __DIR__ . '/../inc/Workspace/WorkspaceAliasResolver.php'; include __DIR__ . '/../inc/Workspace/Workspace.php'; include __DIR__ . '/../inc/Workspace/WorkspaceReader.php'; diff --git a/tests/smoke-workspace-list-directory.php b/tests/smoke-workspace-list-directory.php index a3913ac9..748e0d7e 100644 --- a/tests/smoke-workspace-list-directory.php +++ b/tests/smoke-workspace-list-directory.php @@ -53,6 +53,7 @@ function size_format( $bytes ): string } include __DIR__ . '/../inc/Support/PathSecurity.php'; + include __DIR__ . '/../inc/Workspace/WorkspaceAliasResolver.php'; include __DIR__ . '/../inc/Workspace/Workspace.php'; include __DIR__ . '/../inc/Workspace/WorkspaceReader.php'; diff --git a/tests/smoke-workspace-local-fallback.php b/tests/smoke-workspace-local-fallback.php index 637b1681..cb867b6a 100644 --- a/tests/smoke-workspace-local-fallback.php +++ b/tests/smoke-workspace-local-fallback.php @@ -21,8 +21,14 @@ class GitHubAbilities class RemoteWorkspaceBackend { public static array $show_input = array(); + public static array $read_input = array(); + public static array $list_input = array(); + public static array $grep_input = array(); public static array $worktree_input = array(); public static bool $show_returns_remote_success = false; + public static bool $read_returns_remote_success = false; + public static bool $list_returns_remote_success = false; + public static bool $grep_returns_remote_success = false; public static bool $worktree_returns_remote_success = false; public static function should_handle(): bool @@ -65,6 +71,65 @@ public function worktree_add( string $repo_name, string $branch, ?string $from = 'Remote workspace repository "wpcom-codebox" is not registered. Call workspace_clone first.' ); } + + public function read_file( string $handle, string $path, int $max_size, ?int $offset = null, ?int $limit = null ): array|\WP_Error + { + self::$read_input = compact('handle', 'path', 'max_size', 'offset', 'limit'); + if ( self::$read_returns_remote_success ) { + return array( + 'success' => true, + 'backend' => 'github_api', + 'content' => 'remote file', + 'path' => $path, + 'size' => 11, + ); + } + + return new \WP_Error( + 'remote_workspace_repo_not_found', + 'Remote workspace repository "wpcom-codebox" is not registered. Call workspace_clone first.' + ); + } + + public function list_directory( string $handle, ?string $path = null ): array|\WP_Error + { + self::$list_input = compact('handle', 'path'); + if ( self::$list_returns_remote_success ) { + return array( + 'success' => true, + 'backend' => 'github_api', + 'repo' => $handle, + 'path' => $path ?? '/', + 'entries' => array(), + ); + } + + return new \WP_Error( + 'remote_workspace_repo_not_found', + 'Remote workspace repository "wpcom-codebox" is not registered. Call workspace_clone first.' + ); + } + + public function grep( string $handle, string $pattern, ?string $path = null, ?string $include_pattern = null, int $max_results = 100, int $context_lines = 0 ): array|\WP_Error + { + self::$grep_input = compact('handle', 'pattern', 'path', 'include_pattern', 'max_results', 'context_lines'); + if ( self::$grep_returns_remote_success ) { + return array( + 'success' => true, + 'backend' => 'github_api', + 'repo' => $handle, + 'path' => $path ?? '/', + 'pattern' => $pattern, + 'matches' => array(), + 'count' => 0, + ); + } + + return new \WP_Error( + 'remote_workspace_repo_not_found', + 'Remote workspace repository "wpcom-codebox" is not registered. Call workspace_clone first.' + ); + } } class Workspace @@ -111,6 +176,64 @@ public function worktree_add( ); } } + + class WorkspaceReader + { + public static array $read_input = array(); + public static array $list_input = array(); + public static array $grep_input = array(); + + public function __construct( private Workspace $workspace ) + { + } + + public function read_file( string $name, string $path, int $max_size, ?int $offset = null, ?int $limit = null ): array + { + self::$read_input = compact('name', 'path', 'max_size', 'offset', 'limit'); + return array( + 'success' => true, + 'content' => 'local file', + 'path' => $path, + 'size' => 10, + ); + } + + public function list_directory( string $name, ?string $path = null ): array + { + self::$list_input = compact('name', 'path'); + return array( + 'success' => true, + 'repo' => $name, + 'path' => $path ?? '/', + 'entries' => array( + array( + 'name' => 'README.md', + 'type' => 'file', + 'size' => 10, + ), + ), + ); + } + + public function grep( string $name, string $pattern, ?string $path = null, ?string $include_pattern = null, int $max_results = 100, int $context_lines = 0 ): array + { + self::$grep_input = compact('name', 'pattern', 'path', 'include_pattern', 'max_results', 'context_lines'); + return array( + 'success' => true, + 'repo' => $name, + 'path' => $path ?? '/', + 'pattern' => $pattern, + 'matches' => array( + array( + 'file' => 'README.md', + 'line' => 1, + 'text' => 'local file', + ), + ), + 'count' => 1, + ); + } + } } namespace DataMachineCode\Support { @@ -201,6 +324,58 @@ function is_wp_error( $value ): bool $assert('workspace path returns local worktree when duplicate remote exists', is_array($duplicate_path) && '/Users/chubes/Developer/wpcom-codebox@fix-listed-primary-resolution' === ( $duplicate_path['path'] ?? '' )); $assert('workspace path marks local worktree handle as existing', is_array($duplicate_path) && true === ( $duplicate_path['exists'] ?? null )); + \DataMachineCode\Workspace\RemoteWorkspaceBackend::$read_returns_remote_success = true; + \DataMachineCode\Workspace\RemoteWorkspaceBackend::$read_input = array(); + \DataMachineCode\Workspace\WorkspaceReader::$read_input = array(); + $read = \DataMachineCode\Abilities\WorkspaceAbilities::readFile( + array( + 'repo' => 'wpcom-codebox', + 'path' => 'README.md', + 'max_size' => 500, + 'offset' => 2, + 'limit' => 3, + ) + ); + + $assert('remote read is not consulted when local primary exists', array() === \DataMachineCode\Workspace\RemoteWorkspaceBackend::$read_input); + $assert('local read attempted for local primary', 'wpcom-codebox' === ( \DataMachineCode\Workspace\WorkspaceReader::$read_input['name'] ?? '' )); + $assert('read preserves options locally', 500 === ( \DataMachineCode\Workspace\WorkspaceReader::$read_input['max_size'] ?? null ) && 2 === ( \DataMachineCode\Workspace\WorkspaceReader::$read_input['offset'] ?? null ) && 3 === ( \DataMachineCode\Workspace\WorkspaceReader::$read_input['limit'] ?? null )); + $assert('read returns local file result', is_array($read) && 'local file' === ( $read['content'] ?? '' )); + + \DataMachineCode\Workspace\RemoteWorkspaceBackend::$list_returns_remote_success = true; + \DataMachineCode\Workspace\RemoteWorkspaceBackend::$list_input = array(); + \DataMachineCode\Workspace\WorkspaceReader::$list_input = array(); + $list = \DataMachineCode\Abilities\WorkspaceAbilities::listDirectory( + array( + 'repo' => 'wpcom-codebox', + 'path' => 'inc', + ) + ); + + $assert('remote list is not consulted when local primary exists', array() === \DataMachineCode\Workspace\RemoteWorkspaceBackend::$list_input); + $assert('local list attempted for local primary', 'wpcom-codebox' === ( \DataMachineCode\Workspace\WorkspaceReader::$list_input['name'] ?? '' )); + $assert('list preserves path locally', 'inc' === ( \DataMachineCode\Workspace\WorkspaceReader::$list_input['path'] ?? '' )); + $assert('list returns local entries', is_array($list) && 'README.md' === ( $list['entries'][0]['name'] ?? '' )); + + \DataMachineCode\Workspace\RemoteWorkspaceBackend::$grep_returns_remote_success = true; + \DataMachineCode\Workspace\RemoteWorkspaceBackend::$grep_input = array(); + \DataMachineCode\Workspace\WorkspaceReader::$grep_input = array(); + $grep = \DataMachineCode\Abilities\WorkspaceAbilities::grepFiles( + array( + 'repo' => 'wpcom-codebox', + 'pattern' => 'local', + 'path' => 'inc', + 'include' => '*.php', + 'max_results' => 7, + 'context_lines' => 2, + ) + ); + + $assert('remote grep is not consulted when local primary exists', array() === \DataMachineCode\Workspace\RemoteWorkspaceBackend::$grep_input); + $assert('local grep attempted for local primary', 'wpcom-codebox' === ( \DataMachineCode\Workspace\WorkspaceReader::$grep_input['name'] ?? '' )); + $assert('grep preserves options locally', '*.php' === ( \DataMachineCode\Workspace\WorkspaceReader::$grep_input['include_pattern'] ?? '' ) && 7 === ( \DataMachineCode\Workspace\WorkspaceReader::$grep_input['max_results'] ?? null ) && 2 === ( \DataMachineCode\Workspace\WorkspaceReader::$grep_input['context_lines'] ?? null )); + $assert('grep returns local matches', is_array($grep) && 1 === ( $grep['count'] ?? null )); + \DataMachineCode\Workspace\RemoteWorkspaceBackend::$worktree_returns_remote_success = true; \DataMachineCode\Workspace\RemoteWorkspaceBackend::$worktree_input = array(); $worktree = \DataMachineCode\Abilities\WorkspaceAbilities::worktreeAdd(