diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-28 14:30:41 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-28 14:30:41 +0900 |
| commit | e97dc6e64c1be4bf78c420b11cec2a18dfd506d4 (patch) | |
| tree | 6e397f696631ca23e931a1873803e0b1f85af915 /crates/shirabe/tests/util | |
| parent | f408ff1ff95ce6e512810ccb10695f773fe5a8b9 (diff) | |
| download | php-shirabe-e97dc6e64c1be4bf78c420b11cec2a18dfd506d4.tar.gz php-shirabe-e97dc6e64c1be4bf78c420b11cec2a18dfd506d4.tar.zst php-shirabe-e97dc6e64c1be4bf78c420b11cec2a18dfd506d4.zip | |
test(tests): use mockall for hand-written interface mocks
Replace hand-written mock/stub structs that re-implemented PHPUnit
mock-builder behavior (record-and-verify, manual call counters,
unreachable!() guards) with mockall::mock! locals across:
- package/loader: MockLoader, VersionGuesserMock
- command: ArchiveManager/RepositoryManager/EventDispatcher mocks
- util: ConfigSource/AuthJson mocks (auth_helper, bitbucket, github,
forgejo, gitlab)
- repository/vcs: github_driver NullConfigSource
- installer: CountingInstaller, RecordingBinaryInstaller, and the
DownloadManager mock (formerly common/downloader_stub.rs, now deleted)
- downloader: download_manager create_downloader_mock
Verification (counts/args) now lives in mockall expectations checked on
drop. installation_manager BinaryInstaller is left hand-written because
its as_binary_presence_interface seam returns Some(&mut self), which
mockall cannot express; io_stub and io_mock are left as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/util')
| -rw-r--r-- | crates/shirabe/tests/util/auth_helper_test.rs | 133 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/bitbucket_test.rs | 199 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/forgejo_test.rs | 123 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/github_test.rs | 123 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/gitlab_test.rs | 77 |
5 files changed, 244 insertions, 411 deletions
diff --git a/crates/shirabe/tests/util/auth_helper_test.rs b/crates/shirabe/tests/util/auth_helper_test.rs index b760fc8..7738afb 100644 --- a/crates/shirabe/tests/util/auth_helper_test.rs +++ b/crates/shirabe/tests/util/auth_helper_test.rs @@ -399,59 +399,23 @@ fn test_is_public_bit_bucket_download_with_non_bitbucket_public_url() { ); } -// Records addConfigSetting calls and serves a configurable getName, mirroring the -// PHPUnit mock of ConfigSourceInterface used by the storeAuth tests. -#[derive(Debug)] -struct ConfigSourceMock { - name: String, - added: Rc<RefCell<Vec<(String, PhpMixed)>>>, -} - -impl ConfigSourceInterface for ConfigSourceMock { - fn add_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _append: bool, - ) -> anyhow::Result<()> { - unreachable!() - } - fn insert_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _reference_name: &str, - _offset: i64, - ) -> anyhow::Result<()> { - unreachable!() - } - fn set_repository_url(&mut self, _name: &str, _url: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_repository(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - self.added.borrow_mut().push((name.to_string(), value)); - Ok(()) - } - fn remove_config_setting(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_property(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> { - unreachable!() - } - fn remove_property(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_link(&mut self, _type: &str, _name: &str, _value: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_link(&mut self, _type: &str, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn get_name(&self) -> String { - self.name.clone() +// Mirrors the PHPUnit mock of ConfigSourceInterface used by the storeAuth tests. +// Methods left without an expectation panic if called. +mockall::mock! { + #[derive(Debug)] + pub ConfigSource {} + impl ConfigSourceInterface for ConfigSource { + fn add_repository(&mut self, name: &str, config: PhpMixed, append: bool) -> anyhow::Result<()>; + fn insert_repository(&mut self, name: &str, config: PhpMixed, reference_name: &str, offset: i64) -> anyhow::Result<()>; + fn set_repository_url(&mut self, name: &str, url: &str) -> anyhow::Result<()>; + fn remove_repository(&mut self, name: &str) -> anyhow::Result<()>; + fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()>; + fn add_property(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_property(&mut self, name: &str) -> anyhow::Result<()>; + fn add_link(&mut self, r#type: &str, name: &str, value: &str) -> anyhow::Result<()>; + fn remove_link(&mut self, r#type: &str, name: &str) -> anyhow::Result<()>; + fn get_name(&self) -> String; } } @@ -474,25 +438,23 @@ fn test_store_auth_automatically() { let origin = "github.com"; expects_authentication(&f.io, origin, "my_username", "my_password"); - let added = Rc::new(RefCell::new(Vec::new())); + let mut source = MockConfigSource::new(); + source + .expect_get_name() + .returning(|| "https://api.gitlab.com/source".to_string()); + let expected = expected_auth_setting("my_username", "my_password"); + source + .expect_add_config_setting() + .times(1) + .withf(move |name, value| name == "http-basic.github.com" && *value == expected) + .returning(|_, _| Ok(())); f.config .borrow_mut() - .set_auth_config_source(Box::new(ConfigSourceMock { - name: "https://api.gitlab.com/source".to_string(), - added: added.clone(), - })); + .set_auth_config_source(Box::new(source)); f.auth_helper .store_auth(origin, StoreAuth::Bool(true)) .unwrap(); - - assert_eq!( - *added.borrow(), - vec![( - "http-basic.github.com".to_string(), - expected_auth_setting("my_username", "my_password"), - )] - ); } #[test] @@ -515,23 +477,22 @@ fn test_store_auth_with_prompt_yes_answer() { ) .unwrap(); - let added = Rc::new(RefCell::new(Vec::new())); + let mut source = MockConfigSource::new(); + source + .expect_get_name() + .times(1) + .returning(move || config_source_name.to_string()); + let expected = expected_auth_setting("my_username", "my_password"); + source + .expect_add_config_setting() + .times(1) + .withf(move |name, value| name == "http-basic.github.com" && *value == expected) + .returning(|_, _| Ok(())); f.config .borrow_mut() - .set_auth_config_source(Box::new(ConfigSourceMock { - name: config_source_name.to_string(), - added: added.clone(), - })); + .set_auth_config_source(Box::new(source)); f.auth_helper.store_auth(origin, StoreAuth::Prompt).unwrap(); - - assert_eq!( - *added.borrow(), - vec![( - "http-basic.github.com".to_string(), - expected_auth_setting("my_username", "my_password"), - )] - ); } #[test] @@ -553,17 +514,17 @@ fn test_store_auth_with_prompt_no_answer() { ) .unwrap(); - let added = Rc::new(RefCell::new(Vec::new())); + let mut source = MockConfigSource::new(); + source + .expect_get_name() + .times(1) + .returning(move || config_source_name.to_string()); + source.expect_add_config_setting().times(0); f.config .borrow_mut() - .set_auth_config_source(Box::new(ConfigSourceMock { - name: config_source_name.to_string(), - added: added.clone(), - })); + .set_auth_config_source(Box::new(source)); f.auth_helper.store_auth(origin, StoreAuth::Prompt).unwrap(); - - assert!(added.borrow().is_empty()); } #[test] diff --git a/crates/shirabe/tests/util/bitbucket_test.rs b/crates/shirabe/tests/util/bitbucket_test.rs index d57a49a..2817266 100644 --- a/crates/shirabe/tests/util/bitbucket_test.rs +++ b/crates/shirabe/tests/util/bitbucket_test.rs @@ -27,70 +27,35 @@ const MESSAGE: &str = "mymessage"; const ORIGIN: &str = "bitbucket.org"; const TOKEN: &str = "bitbuckettoken"; -// Records add/removeConfigSetting calls and serves a configurable getName, mirroring -// the PHPUnit mock of ConfigSourceInterface used throughout BitbucketTest. -#[derive(Debug, Clone, Default)] -struct ConfigSourceCalls { - added: Vec<(String, PhpMixed)>, - removed: Vec<String>, +// Mirrors the PHPUnit mock of ConfigSourceInterface used throughout BitbucketTest. +// Methods left without an expectation panic if called. +mockall::mock! { + #[derive(Debug)] + pub ConfigSource {} + impl ConfigSourceInterface for ConfigSource { + fn add_repository(&mut self, name: &str, config: PhpMixed, append: bool) -> anyhow::Result<()>; + fn insert_repository(&mut self, name: &str, config: PhpMixed, reference_name: &str, offset: i64) -> anyhow::Result<()>; + fn set_repository_url(&mut self, name: &str, url: &str) -> anyhow::Result<()>; + fn remove_repository(&mut self, name: &str) -> anyhow::Result<()>; + fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()>; + fn add_property(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_property(&mut self, name: &str) -> anyhow::Result<()>; + fn add_link(&mut self, r#type: &str, name: &str, value: &str) -> anyhow::Result<()>; + fn remove_link(&mut self, r#type: &str, name: &str) -> anyhow::Result<()>; + fn get_name(&self) -> String; + } } -#[derive(Debug)] -struct ConfigSourceMock { - name: String, - calls: Rc<RefCell<ConfigSourceCalls>>, -} - -impl ConfigSourceInterface for ConfigSourceMock { - fn add_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _append: bool, - ) -> anyhow::Result<()> { - unreachable!() - } - fn insert_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _reference_name: &str, - _offset: i64, - ) -> anyhow::Result<()> { - unreachable!() - } - fn set_repository_url(&mut self, _name: &str, _url: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_repository(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - self.calls - .borrow_mut() - .added - .push((name.to_string(), value)); - Ok(()) - } - fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()> { - self.calls.borrow_mut().removed.push(name.to_string()); - Ok(()) - } - fn add_property(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> { - unreachable!() - } - fn remove_property(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_link(&mut self, _type: &str, _name: &str, _value: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_link(&mut self, _type: &str, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn get_name(&self) -> String { - self.name.clone() - } +// A bare auth config source placeholder: getAuthConfigSource() is consulted (e.g. while +// printing instructions), but no add/removeConfigSetting expectations are asserted. +fn placeholder_auth_config_source() -> Box<MockConfigSource> { + let mut mock = MockConfigSource::new(); + mock.expect_get_name() + .returning(|| "auth-config-source".to_string()); + mock.expect_add_config_setting().returning(|_, _| Ok(())); + mock.expect_remove_config_setting().returning(|_| Ok(())); + Box::new(mock) } // Mirrors BitbucketTest::setUp: a DEBUG-verbosity IOMock, a mocked HttpDownloader, a @@ -160,34 +125,50 @@ fn access_token_body() -> String { ) } -// Installs recording config/auth config sources and returns their shared call logs, -// mirroring BitbucketTest::setExpectationsForStoringAccessToken. -struct StoreAccessTokenMocks { - config_source: Rc<RefCell<ConfigSourceCalls>>, - auth_config_source: Rc<RefCell<ConfigSourceCalls>>, -} - +// Installs config/auth config source mocks with the access-token storing expectations, +// mirroring BitbucketTest::setExpectationsForStoringAccessToken. Verification happens +// when the mocks are dropped together with the Config. fn set_expectations_for_storing_access_token( config: &Rc<RefCell<Config>>, -) -> StoreAccessTokenMocks { - let config_source = Rc::new(RefCell::new(ConfigSourceCalls::default())); - let auth_config_source = Rc::new(RefCell::new(ConfigSourceCalls::default())); + time: i64, + remove_basic_auth: bool, +) { + let mut config_source = MockConfigSource::new(); + config_source + .expect_get_name() + .returning(|| "config-source".to_string()); + let removed_key = format!("bitbucket-oauth.{}", ORIGIN); + config_source + .expect_remove_config_setting() + .times(1) + .withf(move |name| name == removed_key) + .returning(|_| Ok(())); config .borrow_mut() - .set_config_source(Box::new(ConfigSourceMock { - name: "config-source".to_string(), - calls: config_source.clone(), - })); + .set_config_source(Box::new(config_source)); + + let mut auth_config_source = MockConfigSource::new(); + auth_config_source + .expect_get_name() + .returning(|| "auth-config-source".to_string()); + let added_key = format!("bitbucket-oauth.{}", ORIGIN); + let expected_value = expected_stored_token(time); + auth_config_source + .expect_add_config_setting() + .times(1) + .withf(move |name, value| name == added_key && *value == expected_value) + .returning(|_, _| Ok(())); + if remove_basic_auth { + let basic_key = format!("http-basic.{}", ORIGIN); + auth_config_source + .expect_remove_config_setting() + .times(1) + .withf(move |name| name == basic_key) + .returning(|_| Ok(())); + } config .borrow_mut() - .set_auth_config_source(Box::new(ConfigSourceMock { - name: "auth-config-source".to_string(), - calls: auth_config_source.clone(), - })); - StoreAccessTokenMocks { - config_source, - auth_config_source, - } + .set_auth_config_source(Box::new(auth_config_source)); } fn expected_stored_token(time: i64) -> PhpMixed { @@ -211,26 +192,6 @@ fn expected_stored_token(time: i64) -> PhpMixed { PhpMixed::Array(consumer) } -fn assert_stored_access_token(mocks: &StoreAccessTokenMocks, time: i64, remove_basic_auth: bool) { - assert_eq!( - mocks.config_source.borrow().removed, - vec![format!("bitbucket-oauth.{}", ORIGIN)], - ); - assert_eq!( - mocks.auth_config_source.borrow().added, - vec![( - format!("bitbucket-oauth.{}", ORIGIN), - expected_stored_token(time) - )], - ); - if remove_basic_auth { - assert_eq!( - mocks.auth_config_source.borrow().removed, - vec![format!("http-basic.{}", ORIGIN)], - ); - } -} - #[test] fn test_request_access_token_with_valid_oauth_consumer() { let config = ConfigStubBuilder::new().build_shared(); @@ -256,7 +217,7 @@ fn test_request_access_token_with_valid_oauth_consumer() { ) .unwrap(); - let mocks = set_expectations_for_storing_access_token(&f.config); + set_expectations_for_storing_access_token(&f.config, f.time, false); assert_eq!( f.bitbucket @@ -264,8 +225,6 @@ fn test_request_access_token_with_valid_oauth_consumer() { .unwrap(), TOKEN ); - - assert_stored_access_token(&mocks, f.time, false); } #[test] @@ -357,7 +316,7 @@ fn test_request_access_token_with_valid_oauth_consumer_and_expired_access_token( ) .unwrap(); - let mocks = set_expectations_for_storing_access_token(&f.config); + set_expectations_for_storing_access_token(&f.config, f.time, false); assert_eq!( f.bitbucket @@ -365,8 +324,6 @@ fn test_request_access_token_with_valid_oauth_consumer_and_expired_access_token( .unwrap(), TOKEN ); - - assert_stored_access_token(&mocks, f.time, false); } #[test] @@ -505,15 +462,13 @@ fn test_username_password_authentication_flow() { ) .unwrap(); - let mocks = set_expectations_for_storing_access_token(&f.config); + set_expectations_for_storing_access_token(&f.config, f.time, true); assert!( f.bitbucket .authorize_oauth_interactively(ORIGIN, Some(MESSAGE)) .unwrap() ); - - assert_stored_access_token(&mocks, f.time, true); } #[test] @@ -522,13 +477,9 @@ fn test_authorize_oauth_interactively_with_empty_username() { let mut f = set_up_with_config_and_http(config, vec![]); // getAuthConfigSource() is consulted while printing the instructions. - let auth_calls = Rc::new(RefCell::new(ConfigSourceCalls::default())); f.config .borrow_mut() - .set_auth_config_source(Box::new(ConfigSourceMock { - name: "auth-config-source".to_string(), - calls: auth_calls, - })); + .set_auth_config_source(placeholder_auth_config_source()); f.io.borrow_mut() .expects(vec![Expectation::ask("Consumer Key (hidden): ", "")], false) @@ -546,13 +497,9 @@ fn test_authorize_oauth_interactively_with_empty_password() { let config = ConfigStubBuilder::new().build_shared(); let mut f = set_up_with_config_and_http(config, vec![]); - let auth_calls = Rc::new(RefCell::new(ConfigSourceCalls::default())); f.config .borrow_mut() - .set_auth_config_source(Box::new(ConfigSourceMock { - name: "auth-config-source".to_string(), - calls: auth_calls, - })); + .set_auth_config_source(placeholder_auth_config_source()); f.io.borrow_mut() .expects( @@ -579,13 +526,9 @@ fn test_authorize_oauth_interactively_with_request_access_token_failure() { // A 400 status makes the mocked HttpDownloader raise a TransportException(400). let mut f = set_up_with_config_and_http(config, vec![expect_full(url, None, 400, "", vec![])]); - let auth_calls = Rc::new(RefCell::new(ConfigSourceCalls::default())); f.config .borrow_mut() - .set_auth_config_source(Box::new(ConfigSourceMock { - name: "auth-config-source".to_string(), - calls: auth_calls, - })); + .set_auth_config_source(placeholder_auth_config_source()); f.io.borrow_mut() .expects( diff --git a/crates/shirabe/tests/util/forgejo_test.rs b/crates/shirabe/tests/util/forgejo_test.rs index fd6dbc9..a1a8f9c 100644 --- a/crates/shirabe/tests/util/forgejo_test.rs +++ b/crates/shirabe/tests/util/forgejo_test.rs @@ -19,74 +19,49 @@ const ACCESS_TOKEN: &str = "access-token"; const MESSAGE: &str = "mymessage"; const ORIGIN: &str = "codeberg.org"; -// Records the config setting names a source has had removed, plus a fixed getName, -// mirroring ForgejoTest's JsonConfigSource mocks (getName -> "auth.json", and the -// config source stubbing removeConfigSetting('forgejo-token.<origin>')). -#[derive(Debug)] -struct ConfigSourceMock { - name: String, - removed: Rc<RefCell<Vec<String>>>, +// Mirrors ForgejoTest's JsonConfigSource mocks. Methods left without an expectation +// panic if called. +mockall::mock! { + #[derive(Debug)] + pub ConfigSource {} + impl ConfigSourceInterface for ConfigSource { + fn add_repository(&mut self, name: &str, config: PhpMixed, append: bool) -> anyhow::Result<()>; + fn insert_repository(&mut self, name: &str, config: PhpMixed, reference_name: &str, offset: i64) -> anyhow::Result<()>; + fn set_repository_url(&mut self, name: &str, url: &str) -> anyhow::Result<()>; + fn remove_repository(&mut self, name: &str) -> anyhow::Result<()>; + fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()>; + fn add_property(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_property(&mut self, name: &str) -> anyhow::Result<()>; + fn add_link(&mut self, r#type: &str, name: &str, value: &str) -> anyhow::Result<()>; + fn remove_link(&mut self, r#type: &str, name: &str) -> anyhow::Result<()>; + fn get_name(&self) -> String; + } } -impl ConfigSourceMock { - fn new(name: &str) -> (Box<Self>, Rc<RefCell<Vec<String>>>) { - let removed = Rc::new(RefCell::new(Vec::new())); - ( - Box::new(Self { - name: name.to_string(), - removed: removed.clone(), - }), - removed, - ) - } +// getAuthJsonMock: getName atLeastOnce -> "auth.json". +fn get_auth_json_mock() -> Box<MockConfigSource> { + let mut mock = MockConfigSource::new(); + mock.expect_get_name() + .times(1..) + .returning(|| "auth.json".to_string()); + mock.expect_add_config_setting().returning(|_, _| Ok(())); + mock.expect_remove_config_setting().returning(|_| Ok(())); + Box::new(mock) } -impl ConfigSourceInterface for ConfigSourceMock { - fn add_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _append: bool, - ) -> anyhow::Result<()> { - unreachable!() - } - fn insert_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _reference_name: &str, - _offset: i64, - ) -> anyhow::Result<()> { - unreachable!() - } - fn set_repository_url(&mut self, _name: &str, _url: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_repository(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_config_setting(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> { - Ok(()) - } - fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()> { - self.removed.borrow_mut().push(name.to_string()); - Ok(()) - } - fn add_property(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> { - unreachable!() - } - fn remove_property(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_link(&mut self, _type: &str, _name: &str, _value: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_link(&mut self, _type: &str, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn get_name(&self) -> String { - self.name.clone() - } +// getConfJsonMock: removeConfigSetting atLeastOnce with('forgejo-token.<origin>'). +fn get_conf_json_mock(origin: &str) -> Box<MockConfigSource> { + let mut mock = MockConfigSource::new(); + let expected = format!("forgejo-token.{}", origin); + mock.expect_remove_config_setting() + .times(1..) + .withf(move |name| name == expected) + .returning(|_| Ok(())); + mock.expect_add_config_setting().returning(|_, _| Ok(())); + mock.expect_get_name() + .returning(|| "config.json".to_string()); + Box::new(mock) } fn build_forgejo( @@ -126,10 +101,12 @@ fn test_username_password_authentication_flow() { ); let config = ConfigStubBuilder::new().build_shared(); - let (auth_source, _) = ConfigSourceMock::new("auth.json"); - let (conf_source, conf_removed) = ConfigSourceMock::new("config.json"); - config.borrow_mut().set_auth_config_source(auth_source); - config.borrow_mut().set_config_source(conf_source); + config + .borrow_mut() + .set_auth_config_source(get_auth_json_mock()); + config + .borrow_mut() + .set_config_source(get_conf_json_mock(ORIGIN)); let mut forgejo = build_forgejo(&io_mock, config, http_downloader); @@ -139,10 +116,7 @@ fn test_username_password_authentication_flow() { .unwrap() .unwrap() ); - assert_eq!( - *conf_removed.borrow(), - vec![format!("forgejo-token.{}", ORIGIN)] - ); + // removeConfigSetting('forgejo-token.<origin>') verified on the conf source mock drop. } #[test] @@ -172,8 +146,9 @@ fn test_username_password_failure() { ); let config = ConfigStubBuilder::new().build_shared(); - let (auth_source, _) = ConfigSourceMock::new("auth.json"); - config.borrow_mut().set_auth_config_source(auth_source); + config + .borrow_mut() + .set_auth_config_source(get_auth_json_mock()); let mut forgejo = build_forgejo(&io_mock, config, http_downloader); diff --git a/crates/shirabe/tests/util/github_test.rs b/crates/shirabe/tests/util/github_test.rs index 10b5dd7..0069a9c 100644 --- a/crates/shirabe/tests/util/github_test.rs +++ b/crates/shirabe/tests/util/github_test.rs @@ -18,74 +18,49 @@ const PASSWORD: &str = "password"; const MESSAGE: &str = "mymessage"; const ORIGIN: &str = "github.com"; -// Records the config setting names a source has had removed, plus a fixed getName, -// mirroring GitHubTest's JsonConfigSource mocks (getName -> "auth.json", and the -// config source stubbing removeConfigSetting('github-oauth.<origin>')). -#[derive(Debug)] -struct ConfigSourceMock { - name: String, - removed: Rc<RefCell<Vec<String>>>, +// Mirrors GitHubTest's JsonConfigSource mocks. Methods left without an expectation +// panic if called. +mockall::mock! { + #[derive(Debug)] + pub ConfigSource {} + impl ConfigSourceInterface for ConfigSource { + fn add_repository(&mut self, name: &str, config: PhpMixed, append: bool) -> anyhow::Result<()>; + fn insert_repository(&mut self, name: &str, config: PhpMixed, reference_name: &str, offset: i64) -> anyhow::Result<()>; + fn set_repository_url(&mut self, name: &str, url: &str) -> anyhow::Result<()>; + fn remove_repository(&mut self, name: &str) -> anyhow::Result<()>; + fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()>; + fn add_property(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_property(&mut self, name: &str) -> anyhow::Result<()>; + fn add_link(&mut self, r#type: &str, name: &str, value: &str) -> anyhow::Result<()>; + fn remove_link(&mut self, r#type: &str, name: &str) -> anyhow::Result<()>; + fn get_name(&self) -> String; + } } -impl ConfigSourceMock { - fn new(name: &str) -> (Box<Self>, Rc<RefCell<Vec<String>>>) { - let removed = Rc::new(RefCell::new(Vec::new())); - ( - Box::new(Self { - name: name.to_string(), - removed: removed.clone(), - }), - removed, - ) - } +// getAuthJsonMock: getName atLeastOnce -> "auth.json". +fn get_auth_json_mock() -> Box<MockConfigSource> { + let mut mock = MockConfigSource::new(); + mock.expect_get_name() + .times(1..) + .returning(|| "auth.json".to_string()); + mock.expect_add_config_setting().returning(|_, _| Ok(())); + mock.expect_remove_config_setting().returning(|_| Ok(())); + Box::new(mock) } -impl ConfigSourceInterface for ConfigSourceMock { - fn add_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _append: bool, - ) -> anyhow::Result<()> { - unreachable!() - } - fn insert_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _reference_name: &str, - _offset: i64, - ) -> anyhow::Result<()> { - unreachable!() - } - fn set_repository_url(&mut self, _name: &str, _url: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_repository(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_config_setting(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> { - Ok(()) - } - fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()> { - self.removed.borrow_mut().push(name.to_string()); - Ok(()) - } - fn add_property(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> { - unreachable!() - } - fn remove_property(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_link(&mut self, _type: &str, _name: &str, _value: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_link(&mut self, _type: &str, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn get_name(&self) -> String { - self.name.clone() - } +// getConfJsonMock: removeConfigSetting atLeastOnce with('github-oauth.<origin>'). +fn get_conf_json_mock(origin: &str) -> Box<MockConfigSource> { + let mut mock = MockConfigSource::new(); + let expected = format!("github-oauth.{}", origin); + mock.expect_remove_config_setting() + .times(1..) + .withf(move |name| name == expected) + .returning(|_| Ok(())); + mock.expect_add_config_setting().returning(|_, _| Ok(())); + mock.expect_get_name() + .returning(|| "config.json".to_string()); + Box::new(mock) } fn build_github( @@ -133,10 +108,12 @@ fn test_username_password_authentication_flow() { ); let config = build_config(); - let (auth_source, _) = ConfigSourceMock::new("auth.json"); - let (conf_source, conf_removed) = ConfigSourceMock::new("config.json"); - config.borrow_mut().set_auth_config_source(auth_source); - config.borrow_mut().set_config_source(conf_source); + config + .borrow_mut() + .set_auth_config_source(get_auth_json_mock()); + config + .borrow_mut() + .set_config_source(get_conf_json_mock(ORIGIN)); let mut github = build_github(&io_mock, config, http_downloader); @@ -145,10 +122,7 @@ fn test_username_password_authentication_flow() { .authorize_oauth_interactively(ORIGIN, Some(MESSAGE)) .unwrap() ); - assert_eq!( - *conf_removed.borrow(), - vec![format!("github-oauth.{}", ORIGIN)] - ); + // removeConfigSetting('github-oauth.<origin>') verified on the conf source mock drop. } #[test] @@ -172,8 +146,9 @@ fn test_username_password_failure() { ); let config = build_config(); - let (auth_source, _) = ConfigSourceMock::new("auth.json"); - config.borrow_mut().set_auth_config_source(auth_source); + config + .borrow_mut() + .set_auth_config_source(get_auth_json_mock()); let mut github = build_github(&io_mock, config, http_downloader); diff --git a/crates/shirabe/tests/util/gitlab_test.rs b/crates/shirabe/tests/util/gitlab_test.rs index ebf7c08..a3db85b 100644 --- a/crates/shirabe/tests/util/gitlab_test.rs +++ b/crates/shirabe/tests/util/gitlab_test.rs @@ -22,61 +22,40 @@ const TOKEN: &str = "gitlabtoken"; const REFRESHTOKEN: &str = "gitlabrefreshtoken"; // Mirrors GitLabTest::getAuthJsonMock: a JsonConfigSource whose getName() returns -// "auth.json" and whose addConfigSetting is a no-op (the PHP mock never stubs it). -#[derive(Debug)] -struct AuthJsonMock; - -impl ConfigSourceInterface for AuthJsonMock { - fn add_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _append: bool, - ) -> anyhow::Result<()> { - unreachable!() - } - fn insert_repository( - &mut self, - _name: &str, - _config: PhpMixed, - _reference_name: &str, - _offset: i64, - ) -> anyhow::Result<()> { - unreachable!() - } - fn set_repository_url(&mut self, _name: &str, _url: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_repository(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_config_setting(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> { - Ok(()) - } - fn remove_config_setting(&mut self, _name: &str) -> anyhow::Result<()> { - Ok(()) - } - fn add_property(&mut self, _name: &str, _value: PhpMixed) -> anyhow::Result<()> { - unreachable!() - } - fn remove_property(&mut self, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn add_link(&mut self, _type: &str, _name: &str, _value: &str) -> anyhow::Result<()> { - unreachable!() - } - fn remove_link(&mut self, _type: &str, _name: &str) -> anyhow::Result<()> { - unreachable!() - } - fn get_name(&self) -> String { - "auth.json".to_string() +// "auth.json" (atLeastOnce) and whose addConfigSetting is a no-op (the PHP mock +// never stubs it). Methods left without an expectation panic if called. +mockall::mock! { + #[derive(Debug)] + pub AuthJson {} + impl ConfigSourceInterface for AuthJson { + fn add_repository(&mut self, name: &str, config: PhpMixed, append: bool) -> anyhow::Result<()>; + fn insert_repository(&mut self, name: &str, config: PhpMixed, reference_name: &str, offset: i64) -> anyhow::Result<()>; + fn set_repository_url(&mut self, name: &str, url: &str) -> anyhow::Result<()>; + fn remove_repository(&mut self, name: &str) -> anyhow::Result<()>; + fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_config_setting(&mut self, name: &str) -> anyhow::Result<()>; + fn add_property(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; + fn remove_property(&mut self, name: &str) -> anyhow::Result<()>; + fn add_link(&mut self, r#type: &str, name: &str, value: &str) -> anyhow::Result<()>; + fn remove_link(&mut self, r#type: &str, name: &str) -> anyhow::Result<()>; + fn get_name(&self) -> String; } } +fn get_auth_json_mock() -> Box<MockAuthJson> { + let mut mock = MockAuthJson::new(); + mock.expect_get_name() + .times(1..) + .returning(|| "auth.json".to_string()); + mock.expect_add_config_setting().returning(|_, _| Ok(())); + mock.expect_remove_config_setting().returning(|_| Ok(())); + Box::new(mock) +} + fn set_up(io_mock: &Rc<RefCell<IOMock>>, config: &Rc<RefCell<Config>>) { config .borrow_mut() - .set_auth_config_source(Box::new(AuthJsonMock)); + .set_auth_config_source(get_auth_json_mock()); let _ = io_mock; } |
