Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/dropshot-api-manager/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 21 additions & 27 deletions crates/integration-tests/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@ impl TestEnvironment {
&self,
api_ident: &str,
version: &str,
) -> bool {
self.find_versioned_document(api_ident, version).is_some()
) -> anyhow::Result<bool> {
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
Expand All @@ -158,7 +160,8 @@ impl TestEnvironment {
api_ident: &str,
version: &str,
) -> anyhow::Result<bool> {
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);
};
Expand All @@ -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<Utf8PathBuf> {
let files = self
.list_document_files()
.expect("reading document files succeeded");
) -> Result<Option<Utf8PathBuf>> {
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.
Expand All @@ -198,25 +202,15 @@ impl TestEnvironment {
api_ident: &str,
version: &str,
) -> Result<String> {
// 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.
Expand Down
157 changes: 139 additions & 18 deletions crates/integration-tests/tests/integration/versioned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()?;
Expand Down Expand Up @@ -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()?;
Expand Down Expand Up @@ -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(())
}