From 4d9e3dd6176a0cd2cc5e158b044beeb7b3de21be Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 12 Jul 2026 01:28:17 +0900 Subject: test(util): port remaining todo!() tests in util test suite Implement previously-todo!() tests in auth_helper_test.rs, process_executor_test.rs, remote_filesystem_test.rs, and stream_context_factory_test.rs by porting the corresponding PHPUnit test methods. Extend IOStub with writeRaw/setAuthentication call tracking and askAndValidate/getAuthentication overrides to model the PHPUnit mocks these tests rely on, deduping the resulting call-recording fields into a small generic CallRecorder helper instead of repeating the same RefCell> push/borrow().clone() boilerplate five times. testStoreAuthWithPromptInvalidAnswer and testPromptAuthIfNeededMultipleBitbucketDownloads had initially lost the ported PHPUnit mock's argument/call-count assertions (askAndValidate's exact prompt string, and hasAuthentication/getAuthentication's exactly(2) call counts), silently narrowing what the tests verify; IOStub now records these calls and the tests assert on them, matching upstream. Tests left unportable (PHP set_error_handler machinery, closures in data providers, network/subclass-mock dependencies, etc.) keep #[ignore] with a single // TODO(phase-d) reason recorded in the function body. Co-Authored-By: Claude Sonnet 5 --- crates/shirabe/tests/common/io_stub.rs | 93 ++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 5 deletions(-) (limited to 'crates/shirabe/tests/common') diff --git a/crates/shirabe/tests/common/io_stub.rs b/crates/shirabe/tests/common/io_stub.rs index b57dd0ca..d8c60799 100644 --- a/crates/shirabe/tests/common/io_stub.rs +++ b/crates/shirabe/tests/common/io_stub.rs @@ -10,6 +10,21 @@ use shirabe::config::Config; use shirabe::io::{BaseIO, IOInterface, IOInterfaceImmutable, IOInterfaceMutable}; use shirabe_php_shim::PhpMixed; +// Records call arguments for a stubbed method, equivalent to PHPUnit's +// `->expects($this->once())->method(...)->with(...)` call-count/argument verification. +#[derive(Debug, Default)] +struct CallRecorder(std::cell::RefCell>); + +impl CallRecorder { + fn push(&self, value: T) { + self.0.borrow_mut().push(value); + } + + fn calls(&self) -> Vec { + self.0.borrow().clone() + } +} + #[derive(Debug, Default)] pub struct IOStub { authentications: indexmap::IndexMap>>, @@ -27,12 +42,29 @@ pub struct IOStub { get_authentication: Option>>, ask: Option, + // When set, `askAndValidate` invokes the caller's validator with this value and returns/ + // propagates its `Result`, equivalent to a PHPUnit `willReturnCallback` that calls the + // validator directly. When unset, `askAndValidate` just returns `default` (unvalidated). + ask_and_validate_answer: Option, ask_confirmation: Option, ask_and_hide_answer: Option>, // Keyed `askAndHideAnswer` replies, mirroring tests whose willReturnCallback // switches on the question string. Unknown questions return `''` like PHP's // `switch` default. ask_and_hide_answer_responses: Option>, + + // Records `writeRaw` calls. + write_raw_calls: CallRecorder<(String, bool)>, + // Records `setAuthentication` calls. Kept separate from `authentications` so + // `with_has_authentication`/`with_get_authentication` stay static (matching a PHPUnit + // `willReturn`) even while `setAuthentication` is invoked. + set_authentication_calls: CallRecorder<(String, String, Option)>, + // Records `askAndValidate` calls (question, attempts, default). + ask_and_validate_calls: CallRecorder<(String, Option, PhpMixed)>, + // Records `hasAuthentication` calls. + has_authentication_calls: CallRecorder, + // Records `getAuthentication` calls. + get_authentication_calls: CallRecorder, } impl IOStub { @@ -71,10 +103,20 @@ impl IOStub { self.get_authentication = Some(value); self } + // Mutator counterpart of `with_get_authentication`, for tests that reconfigure the stub's + // response between calls (equivalent to PHPUnit's `willReturnCallback` sequencing via + // `array_shift`). + pub fn set_get_authentication(&mut self, value: indexmap::IndexMap>) { + self.get_authentication = Some(value); + } pub fn with_ask(mut self, value: PhpMixed) -> Self { self.ask = Some(value); self } + pub fn with_ask_and_validate_answer(mut self, value: PhpMixed) -> Self { + self.ask_and_validate_answer = Some(value); + self + } pub fn with_ask_confirmation(mut self, value: bool) -> Self { self.ask_confirmation = Some(value); self @@ -106,6 +148,31 @@ impl IOStub { self.authentications.insert(repository_name.into(), auth); self } + + // For testing only. Returns the recorded `writeRaw` calls in call order. + pub fn write_raw_calls(&self) -> Vec<(String, bool)> { + self.write_raw_calls.calls() + } + + // For testing only. Returns the recorded `setAuthentication` calls in call order. + pub fn set_authentication_calls(&self) -> Vec<(String, String, Option)> { + self.set_authentication_calls.calls() + } + + // For testing only. Returns the recorded `askAndValidate` calls in call order. + pub fn ask_and_validate_calls(&self) -> Vec<(String, Option, PhpMixed)> { + self.ask_and_validate_calls.calls() + } + + // For testing only. Returns the recorded `hasAuthentication` calls in call order. + pub fn has_authentication_calls(&self) -> Vec { + self.has_authentication_calls.calls() + } + + // For testing only. Returns the recorded `getAuthentication` calls in call order. + pub fn get_authentication_calls(&self) -> Vec { + self.get_authentication_calls.calls() + } } impl IOInterfaceImmutable for IOStub { @@ -127,7 +194,9 @@ impl IOInterfaceImmutable for IOStub { fn write3(&self, _message: &str, _newline: bool, _verbosity: i64) {} fn write_error3(&self, _message: &str, _newline: bool, _verbosity: i64) {} - fn write_raw3(&self, _message: &str, _newline: bool, _verbosity: i64) {} + fn write_raw3(&self, message: &str, newline: bool, _verbosity: i64) { + self.write_raw_calls.push((message.to_string(), newline)); + } fn write_error_raw3(&self, _message: &str, _newline: bool, _verbosity: i64) {} fn overwrite4(&self, _message: &str, _newline: bool, _size: Option, _verbosity: i64) {} fn overwrite_error4( @@ -147,12 +216,17 @@ impl IOInterfaceImmutable for IOStub { } fn ask_and_validate( &self, - _question: String, - _validator: Box anyhow::Result>, - _attempts: Option, + question: String, + validator: Box anyhow::Result>, + attempts: Option, default: PhpMixed, ) -> anyhow::Result { - Ok(default) + self.ask_and_validate_calls + .push((question, attempts, default.clone())); + match &self.ask_and_validate_answer { + Some(answer) => validator(answer.clone()), + None => Ok(default), + } } fn ask_and_hide_answer(&self, question: String) -> Option { if let Some(responses) = &self.ask_and_hide_answer_responses { @@ -178,6 +252,8 @@ impl IOInterfaceImmutable for IOStub { ::get_authentications(self) } fn has_authentication(&self, repository_name: &str) -> bool { + self.has_authentication_calls + .push(repository_name.to_string()); match self.has_authentication { Some(value) => value, None => ::has_authentication(self, repository_name), @@ -187,6 +263,8 @@ impl IOInterfaceImmutable for IOStub { &self, repository_name: &str, ) -> indexmap::IndexMap> { + self.get_authentication_calls + .push(repository_name.to_string()); match &self.get_authentication { Some(value) => value.clone(), None => ::get_authentication(self, repository_name), @@ -211,6 +289,11 @@ impl IOInterfaceMutable for IOStub { username: String, password: Option, ) { + self.set_authentication_calls.push(( + repository_name.clone(), + username.clone(), + password.clone(), + )); ::set_authentication(self, repository_name, username, password) } fn load_configuration(&mut self, config: &mut Config) -> anyhow::Result<()> { -- cgit v1.3.1