From 4b459ebf8eade9f650672d42100d8ff853983a87 Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 30 Sep 2025 06:59:11 +0000 Subject: [PATCH] [spr] initial version Created using spr 1.3.6-beta.1 --- crates/dropshot-api-manager/src/output.rs | 2 +- crates/integration-tests/src/common/mod.rs | 48 +++--- .../tests/integration/versioned.rs | 157 ++++++++++++++++-- 3 files changed, 161 insertions(+), 46 deletions(-) diff --git a/crates/dropshot-api-manager/src/output.rs b/crates/dropshot-api-manager/src/output.rs index 75589a0..aa240b1 100644 --- a/crates/dropshot-api-manager/src/output.rs +++ b/crates/dropshot-api-manager/src/output.rs @@ -225,7 +225,7 @@ pub fn display_load_problems( let mut nerrors = 0; for e in error_accumulator.iter_errors() { nerrors += 1; - println!( + eprintln!( "{:>HEADER_WIDTH$} {:#}", FAILURE.style(styles.failure_header), e diff --git a/crates/integration-tests/src/common/mod.rs b/crates/integration-tests/src/common/mod.rs index 578c792..5879c75 100644 --- a/crates/integration-tests/src/common/mod.rs +++ b/crates/integration-tests/src/common/mod.rs @@ -147,8 +147,10 @@ impl TestEnvironment { &self, api_ident: &str, version: &str, - ) -> bool { - self.find_versioned_document(api_ident, version).is_some() + ) -> anyhow::Result { + let maybe_path = + self.find_versioned_document_path(api_ident, version)?; + Ok(maybe_path.is_some()) } /// Check that a versioned document exists for a versioned API at a specific @@ -158,7 +160,8 @@ impl TestEnvironment { api_ident: &str, version: &str, ) -> anyhow::Result { - let Some(path) = self.find_versioned_document(api_ident, version) + let Some(path) = + self.find_versioned_document_path(api_ident, version)? else { return Ok(false); }; @@ -172,24 +175,25 @@ impl TestEnvironment { Ok(output.trim() == path) } - fn find_versioned_document( + /// Find the path of a versioned API document for a specific version, + /// relative to the workspace root. + pub fn find_versioned_document_path( &self, api_ident: &str, version: &str, - ) -> Option { - let files = self - .list_document_files() - .expect("reading document files succeeded"); + ) -> Result> { + let files = self.list_document_files()?; // Versioned documents are stored in subdirectories like: // documents/api/api-version-hash.json. let pattern = format!("documents/{}/{}-{}-", api_ident, api_ident, version); - files.iter().find_map(|f| { + let path = files.iter().find_map(|f| { let rel_path = rel_path_forward_slashes(f.as_ref()); rel_path.starts_with(&pattern).then(|| Utf8PathBuf::from(rel_path)) - }) + }); + Ok(path) } /// Read the content of a versioned API document for a specific version. @@ -198,25 +202,15 @@ impl TestEnvironment { api_ident: &str, version: &str, ) -> Result { - // Find the document file that matches the version pattern. - let files = self.list_document_files()?; - let pattern = - format!("documents/{}/{}-{}-", api_ident, api_ident, version); - - let matching_file = files - .iter() - .find(|f| { - rel_path_forward_slashes(f.as_ref()).starts_with(&pattern) - }) - .ok_or_else(|| { - anyhow!( - "No versioned document found for {} version {}", - api_ident, - version + let path = self + .find_versioned_document_path(api_ident, version)? + .with_context(|| { + format!( + "did not find versioned document for {} v{}", + api_ident, version ) })?; - - self.read_file(matching_file) + self.read_file(&path) } /// List all versioned documents for a specific API. diff --git a/crates/integration-tests/tests/integration/versioned.rs b/crates/integration-tests/tests/integration/versioned.rs index 034a280..d300987 100644 --- a/crates/integration-tests/tests/integration/versioned.rs +++ b/crates/integration-tests/tests/integration/versioned.rs @@ -6,7 +6,7 @@ //! OpenAPI document. These are "blessed" documents that are checked into git //! and must remain stable across changes. -use anyhow::Result; +use anyhow::{Context, Result}; use dropshot_api_manager::test_util::{CheckResult, check_apis_up_to_date}; use integration_tests::common::{ create_versioned_health_test_apis_incompatible, @@ -21,18 +21,36 @@ fn test_versioned_generate_basic() -> Result<()> { let apis = create_versioned_health_test_apis()?; // Initially, no documents should exist. - assert!(!env.versioned_local_document_exists("versioned-health", "1.0.0")); - assert!(!env.versioned_local_document_exists("versioned-health", "2.0.0")); - assert!(!env.versioned_local_document_exists("versioned-health", "3.0.0")); + assert!( + !env.versioned_local_document_exists("versioned-health", "1.0.0") + .unwrap() + ); + assert!( + !env.versioned_local_document_exists("versioned-health", "2.0.0") + .unwrap() + ); + assert!( + !env.versioned_local_document_exists("versioned-health", "3.0.0") + .unwrap() + ); assert!(!env.versioned_latest_document_exists("versioned-health")); // Generate the documents. env.generate_documents(&apis)?; // Now the version documents should exist. - assert!(env.versioned_local_document_exists("versioned-health", "1.0.0")); - assert!(env.versioned_local_document_exists("versioned-health", "2.0.0")); - assert!(env.versioned_local_document_exists("versioned-health", "3.0.0")); + assert!( + env.versioned_local_document_exists("versioned-health", "1.0.0") + .unwrap() + ); + assert!( + env.versioned_local_document_exists("versioned-health", "2.0.0") + .unwrap() + ); + assert!( + env.versioned_local_document_exists("versioned-health", "3.0.0") + .unwrap() + ); assert!(env.versioned_latest_document_exists("versioned-health")); // Read and validate one of the documents is valid JSON. @@ -135,15 +153,30 @@ fn test_multiple_versioned_apis() -> Result<()> { // Check that documents exist for both APIs and all their versions. // Versioned health API (3 versions). - assert!(env.versioned_local_document_exists("versioned-health", "1.0.0")); - assert!(env.versioned_local_document_exists("versioned-health", "2.0.0")); - assert!(env.versioned_local_document_exists("versioned-health", "3.0.0")); + assert!( + env.versioned_local_document_exists("versioned-health", "1.0.0") + .unwrap() + ); + assert!( + env.versioned_local_document_exists("versioned-health", "2.0.0") + .unwrap() + ); + assert!( + env.versioned_local_document_exists("versioned-health", "3.0.0") + .unwrap() + ); assert!(env.versioned_latest_document_exists("versioned-health")); // Versioned user API (3 versions). - assert!(env.versioned_local_document_exists("versioned-user", "1.0.0")); - assert!(env.versioned_local_document_exists("versioned-user", "2.0.0")); - assert!(env.versioned_local_document_exists("versioned-user", "3.0.0")); + assert!( + env.versioned_local_document_exists("versioned-user", "1.0.0").unwrap() + ); + assert!( + env.versioned_local_document_exists("versioned-user", "2.0.0").unwrap() + ); + assert!( + env.versioned_local_document_exists("versioned-user", "3.0.0").unwrap() + ); assert!(env.versioned_latest_document_exists("versioned-user")); // List all versioned documents for each API. @@ -171,8 +204,13 @@ fn test_mixed_lockstep_and_versioned_apis() -> Result<()> { assert!(env.lockstep_document_exists("counter")); // Check versioned APIs exist as version-specific files. - assert!(env.versioned_local_document_exists("versioned-health", "1.0.0")); - assert!(env.versioned_local_document_exists("versioned-user", "1.0.0")); + assert!( + env.versioned_local_document_exists("versioned-health", "1.0.0") + .unwrap() + ); + assert!( + env.versioned_local_document_exists("versioned-user", "1.0.0").unwrap() + ); // List all document files to verify proper structure. let all_files = env.list_document_files()?; @@ -328,9 +366,27 @@ fn test_removing_api_version_fails_check() -> Result<()> { env.commit_documents()?; // Verify all versions exist. - assert!(env.versioned_local_document_exists("versioned-health", "1.0.0")); - assert!(env.versioned_local_document_exists("versioned-health", "2.0.0")); - assert!(env.versioned_local_document_exists("versioned-health", "3.0.0")); + assert!( + env.versioned_local_and_blessed_document_exists( + "versioned-health", + "1.0.0" + ) + .unwrap() + ); + assert!( + env.versioned_local_and_blessed_document_exists( + "versioned-health", + "2.0.0" + ) + .unwrap() + ); + assert!( + env.versioned_local_and_blessed_document_exists( + "versioned-health", + "3.0.0" + ) + .unwrap() + ); // Create API with fewer versions (simulating version removal). let reduced_apis = create_versioned_health_test_apis_reduced_versions()?; @@ -652,3 +708,68 @@ fn test_incompatible_blessed_api_change() -> Result<()> { Ok(()) } + +/// Test BlessedVersionExtraLocalSpec problems. +/// +/// This test: +/// +/// * creates blessed versions +/// * in a separate environment, creates another blessed version +/// * copies over this extra version +#[test] +fn test_blessed_version_extra_local_spec() -> Result<()> { + let env = TestEnvironment::new()?; + let apis = create_versioned_health_test_apis()?; + + // Generate and commit initial documents to make them blessed. + env.generate_documents(&apis)?; + env.commit_documents()?; + + // Verify initial state is up-to-date. + let result = check_apis_up_to_date(env.environment(), &apis)?; + assert_eq!(result, CheckResult::Success); + + // Generate with the incompatible APIs. + let env2 = TestEnvironment::new()?; + let incompatible_apis = create_versioned_health_test_apis_incompatible()?; + + env2.generate_documents(&incompatible_apis)?; + + // Ensure that the v3 documents are actually different between env and env2. + let env_path = env + .find_versioned_document_path("versioned-health", "3.0.0")? + .expect("should find v3.0.0 document"); + let env2_path = env2 + .find_versioned_document_path("versioned-health", "3.0.0")? + .expect("should find v3.0.0 document"); + assert_ne!( + env_path, env2_path, + "incompatible APIs should lead to different hashes" + ); + + // Copy env2's document into env's documents directory. + let src = env2.workspace_root().join(&env2_path); + let dst = env + .documents_dir() + .join("versioned-health") + .join(env2_path.file_name().unwrap()); + + std::fs::copy(&src, &dst) + .with_context(|| format!("failed to copy {} to {}", src, dst))?; + assert!(dst.exists(), "destination path {dst} exists"); + + let result = check_apis_up_to_date(env.environment(), &apis)?; + assert_eq!(result, CheckResult::NeedsUpdate); + + // Regenerating documents should remove the file. + env.generate_documents(&apis)?; + + // After fix-up, should be up-to-date again. + let result = check_apis_up_to_date(env.environment(), &apis)?; + assert_eq!(result, CheckResult::Success); + + // The destination path should be missing now. + assert!(!dst.exists(), "destination path {dst} no longer exists"); + + Ok(()) +}