v0.4.7-pre.013
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs
|
||||
// version: 2
|
||||
// version: 4
|
||||
|
||||
//! Deterministic Metaplex Token Metadata scenario inventory for tests and Devnet campaigns.
|
||||
//! Coherent Metaplex Token Metadata scenario journeys for tests and Devnet campaigns.
|
||||
|
||||
/// Stable asset family covered by one Metaplex scenario.
|
||||
#[derive(
|
||||
@@ -15,12 +15,52 @@ pub enum MetaplexTokenMetadataAssetFamily {
|
||||
Sft,
|
||||
/// Fungible token with Metaplex metadata.
|
||||
Fungible,
|
||||
/// Collection metadata and verification flow.
|
||||
/// Collection parent and member relationship.
|
||||
Collection,
|
||||
/// Programmable non-fungible token.
|
||||
ProgrammableNft,
|
||||
}
|
||||
|
||||
/// Stable fixture graph required by one scenario journey.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MetaplexTokenMetadataFixtureKind {
|
||||
/// Fresh zero-decimal classic SPL mint with profile-owned authorities.
|
||||
ClassicNftMint,
|
||||
/// Fresh SFT mint and token account.
|
||||
SemiFungibleMint,
|
||||
/// Fresh fungible mint and token account.
|
||||
FungibleMint,
|
||||
/// Collection parent plus one unverified member.
|
||||
CollectionParentAndMember,
|
||||
/// Programmable NFT with token record and optional rule set.
|
||||
ProgrammableNftTokenRecord,
|
||||
}
|
||||
|
||||
/// Stable lifecycle state consumed or produced by one scenario journey.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MetaplexTokenMetadataFixtureState {
|
||||
/// Mint exists but metadata does not.
|
||||
MintPrepared,
|
||||
/// Metadata exists and remains mutable.
|
||||
MetadataCreated,
|
||||
/// Master edition exists.
|
||||
MasterEditionCreated,
|
||||
/// Collection member exists but is not verified.
|
||||
CollectionMemberUnverified,
|
||||
/// Collection member is verified.
|
||||
CollectionMemberVerified,
|
||||
/// Programmable token record exists.
|
||||
ProgrammableTokenRecordCreated,
|
||||
/// Programmable asset is delegated.
|
||||
Delegated,
|
||||
/// Programmable asset is locked.
|
||||
Locked,
|
||||
/// Scenario has reached its terminal validated state.
|
||||
Validated,
|
||||
}
|
||||
|
||||
/// Execution mode accepted by one scenario.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -33,13 +73,21 @@ pub enum MetaplexTokenMetadataScenarioMode {
|
||||
NetworkSubmission,
|
||||
}
|
||||
|
||||
/// One stable Metaplex scenario reusable by automated tests and demo adapters.
|
||||
/// One stable Metaplex journey reusable by automated tests and demo adapters.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct MetaplexTokenMetadataScenario {
|
||||
/// Stable scenario identifier.
|
||||
pub id: std::string::String,
|
||||
/// Operator-visible scenario label.
|
||||
pub label: std::string::String,
|
||||
/// Asset family exercised by the scenario.
|
||||
pub asset_family: crate::MetaplexTokenMetadataAssetFamily,
|
||||
/// Fixture graph required by the scenario.
|
||||
pub fixture_kind: crate::MetaplexTokenMetadataFixtureKind,
|
||||
/// Required initial lifecycle state.
|
||||
pub initial_state: crate::MetaplexTokenMetadataFixtureState,
|
||||
/// Expected terminal lifecycle state.
|
||||
pub resulting_state: crate::MetaplexTokenMetadataFixtureState,
|
||||
/// Execution mode.
|
||||
pub mode: crate::MetaplexTokenMetadataScenarioMode,
|
||||
/// Stable operation codes executed in order.
|
||||
@@ -50,93 +98,155 @@ pub struct MetaplexTokenMetadataScenario {
|
||||
pub requires_programmable_rules: bool,
|
||||
/// Whether a confirmed postcondition is mandatory.
|
||||
pub requires_postcondition: bool,
|
||||
/// Canonical account projections expected when materialization is enabled.
|
||||
pub materialization_targets: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
/// Returns the complete ordered synthetic Metaplex scenario inventory.
|
||||
fn scenario(
|
||||
id: &str,
|
||||
label: &str,
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily,
|
||||
fixture_kind: crate::MetaplexTokenMetadataFixtureKind,
|
||||
initial_state: crate::MetaplexTokenMetadataFixtureState,
|
||||
resulting_state: crate::MetaplexTokenMetadataFixtureState,
|
||||
operation_codes: std::vec::Vec<std::string::String>,
|
||||
requires_collection: bool,
|
||||
requires_programmable_rules: bool,
|
||||
materialization_targets: &[&str],
|
||||
) -> crate::MetaplexTokenMetadataScenario {
|
||||
return crate::MetaplexTokenMetadataScenario {
|
||||
id: id.to_string(),
|
||||
label: label.to_string(),
|
||||
asset_family,
|
||||
fixture_kind,
|
||||
initial_state,
|
||||
resulting_state,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes,
|
||||
requires_collection,
|
||||
requires_programmable_rules,
|
||||
requires_postcondition: true,
|
||||
materialization_targets: materialization_targets
|
||||
.iter()
|
||||
.map(|value| return (*value).to_string())
|
||||
.collect(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the complete ordered synthetic Metaplex journey inventory.
|
||||
pub fn metaplex_token_metadata_synthetic_scenarios()
|
||||
-> std::vec::Vec<crate::MetaplexTokenMetadataScenario> {
|
||||
return vec![
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_nft_create_update".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::Nft,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
scenario(
|
||||
"metaplex_nft_create_update",
|
||||
"NFT classique : création, mise à jour et édition maître",
|
||||
crate::MetaplexTokenMetadataAssetFamily::Nft,
|
||||
crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint,
|
||||
crate::MetaplexTokenMetadataFixtureState::MintPrepared,
|
||||
crate::MetaplexTokenMetadataFixtureState::MasterEditionCreated,
|
||||
vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: false,
|
||||
requires_programmable_rules: false,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_sft_create_mint".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::Sft,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
false,
|
||||
false,
|
||||
&["metadata_state", "edition_state"],
|
||||
),
|
||||
scenario(
|
||||
"metaplex_sft_create_mint",
|
||||
"SFT : création des metadata puis émission de supply",
|
||||
crate::MetaplexTokenMetadataAssetFamily::Sft,
|
||||
crate::MetaplexTokenMetadataFixtureKind::SemiFungibleMint,
|
||||
crate::MetaplexTokenMetadataFixtureState::MintPrepared,
|
||||
crate::MetaplexTokenMetadataFixtureState::Validated,
|
||||
vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_MINT_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: false,
|
||||
requires_programmable_rules: false,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_fungible_create_update".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::Fungible,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
false,
|
||||
false,
|
||||
&["metadata_state"],
|
||||
),
|
||||
scenario(
|
||||
"metaplex_fungible_create_update",
|
||||
"Fungible : création et transitions d’autorité",
|
||||
crate::MetaplexTokenMetadataAssetFamily::Fungible,
|
||||
crate::MetaplexTokenMetadataFixtureKind::FungibleMint,
|
||||
crate::MetaplexTokenMetadataFixtureState::MintPrepared,
|
||||
crate::MetaplexTokenMetadataFixtureState::Validated,
|
||||
vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: false,
|
||||
requires_programmable_rules: false,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_collection_verify_unverify".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::Collection,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
false,
|
||||
false,
|
||||
&["metadata_state"],
|
||||
),
|
||||
scenario(
|
||||
"metaplex_collection_verify_unverify",
|
||||
"Collection : membre non vérifié, vérification puis révocation",
|
||||
crate::MetaplexTokenMetadataAssetFamily::Collection,
|
||||
crate::MetaplexTokenMetadataFixtureKind::CollectionParentAndMember,
|
||||
crate::MetaplexTokenMetadataFixtureState::CollectionMemberUnverified,
|
||||
crate::MetaplexTokenMetadataFixtureState::CollectionMemberVerified,
|
||||
vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_VERIFY_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UNVERIFY_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_SET_COLLECTION_SIZE_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: true,
|
||||
requires_programmable_rules: false,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_pnft_delegate_transfer_revoke".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
true,
|
||||
false,
|
||||
&["metadata_state", "edition_state"],
|
||||
),
|
||||
scenario(
|
||||
"metaplex_pnft_delegate_transfer_revoke",
|
||||
"pNFT : token record, délégation, verrouillage, transfert et révocation",
|
||||
crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
|
||||
crate::MetaplexTokenMetadataFixtureKind::ProgrammableNftTokenRecord,
|
||||
crate::MetaplexTokenMetadataFixtureState::ProgrammableTokenRecordCreated,
|
||||
crate::MetaplexTokenMetadataFixtureState::Validated,
|
||||
vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_DELEGATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_LOCK_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UNLOCK_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_TRANSFER_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_REVOKE_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: false,
|
||||
requires_programmable_rules: true,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
false,
|
||||
true,
|
||||
&["metadata_state", "programmable_token_state", "delegate_admin_state"],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/// Returns the ordered Metaplex scenarios intended for opt-in Devnet simulation.
|
||||
/// Returns the ordered Metaplex journeys intended for opt-in Devnet simulation.
|
||||
pub fn metaplex_token_metadata_devnet_scenarios()
|
||||
-> std::vec::Vec<crate::MetaplexTokenMetadataScenario> {
|
||||
return crate::metaplex_token_metadata_synthetic_scenarios()
|
||||
.into_iter()
|
||||
.map(|mut scenario| {
|
||||
scenario.id = format!("{}_devnet_simulation", scenario.id);
|
||||
scenario.mode = crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation;
|
||||
return scenario;
|
||||
})
|
||||
.collect::<std::vec::Vec<crate::MetaplexTokenMetadataScenario>>();
|
||||
let mut scenario = scenario(
|
||||
"metaplex_nft_create_update_devnet",
|
||||
"NFT classique : création confirmée puis mise à jour",
|
||||
crate::MetaplexTokenMetadataAssetFamily::Nft,
|
||||
crate::MetaplexTokenMetadataFixtureKind::ClassicNftMint,
|
||||
crate::MetaplexTokenMetadataFixtureState::MintPrepared,
|
||||
crate::MetaplexTokenMetadataFixtureState::MetadataCreated,
|
||||
vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
|
||||
],
|
||||
false,
|
||||
false,
|
||||
&["metadata_state", "edition_state"],
|
||||
);
|
||||
scenario.mode = crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation;
|
||||
return vec![scenario];
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn synthetic_inventory_is_exact_unique_and_covers_every_required_asset_family() {
|
||||
fn inventory_is_exact_unique_and_uses_coherent_fixture_graphs() {
|
||||
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
|
||||
assert_eq!(scenarios.len(), 5);
|
||||
let ids = scenarios
|
||||
@@ -144,56 +254,43 @@ mod tests {
|
||||
.map(|scenario| return scenario.id.as_str())
|
||||
.collect::<std::collections::BTreeSet<&str>>();
|
||||
assert_eq!(ids.len(), scenarios.len());
|
||||
let families = scenarios
|
||||
.iter()
|
||||
.map(|scenario| return scenario.asset_family)
|
||||
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
|
||||
);
|
||||
assert_eq!(families.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_scenarios_are_simulation_safe_and_require_postconditions() {
|
||||
for scenario in crate::metaplex_token_metadata_synthetic_scenarios() {
|
||||
assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::Synthetic);
|
||||
for scenario in scenarios {
|
||||
assert!(!scenario.label.trim().is_empty());
|
||||
assert!(!scenario.operation_codes.is_empty());
|
||||
assert!(!scenario.materialization_targets.is_empty());
|
||||
assert!(scenario.requires_postcondition);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn devnet_inventory_mirrors_every_asset_family_as_network_simulation() {
|
||||
let synthetic = crate::metaplex_token_metadata_synthetic_scenarios();
|
||||
let devnet = crate::metaplex_token_metadata_devnet_scenarios();
|
||||
assert_eq!(devnet.len(), synthetic.len());
|
||||
assert!(devnet.iter().all(|scenario| return scenario.mode
|
||||
== crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation));
|
||||
assert!(devnet.iter().all(|scenario| return scenario.id.ends_with("_devnet_simulation")));
|
||||
let families = devnet
|
||||
.iter()
|
||||
.map(|scenario| return scenario.asset_family)
|
||||
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
|
||||
);
|
||||
assert_eq!(families.len(), 5);
|
||||
fn collection_and_programmable_journeys_declare_special_requirements() {
|
||||
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
|
||||
let collection = scenarios.iter().find(|scenario| {
|
||||
return scenario.asset_family == crate::MetaplexTokenMetadataAssetFamily::Collection;
|
||||
});
|
||||
assert!(collection.is_some_and(|scenario| return scenario.requires_collection));
|
||||
let programmable = scenarios.iter().find(|scenario| {
|
||||
return scenario.asset_family
|
||||
== crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft;
|
||||
});
|
||||
assert!(programmable.is_some_and(|scenario| return scenario.requires_programmable_rules));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn programmable_and_collection_requirements_are_declared_only_where_needed() {
|
||||
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
|
||||
let programmable = scenarios
|
||||
.iter()
|
||||
.filter(|scenario| return scenario.requires_programmable_rules)
|
||||
.collect::<std::vec::Vec<&crate::MetaplexTokenMetadataScenario>>();
|
||||
assert_eq!(programmable.len(), 1);
|
||||
fn devnet_inventory_exposes_only_fully_preparable_journeys() {
|
||||
let devnet = crate::metaplex_token_metadata_devnet_scenarios();
|
||||
assert_eq!(devnet.len(), 1);
|
||||
let scenario = &devnet[0];
|
||||
assert_eq!(scenario.asset_family, crate::MetaplexTokenMetadataAssetFamily::Nft);
|
||||
assert_eq!(scenario.operation_codes.len(), 2);
|
||||
assert_eq!(
|
||||
programmable[0].asset_family,
|
||||
crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft
|
||||
scenario.operation_codes[0],
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION
|
||||
);
|
||||
let collection = scenarios
|
||||
.iter()
|
||||
.filter(|scenario| return scenario.requires_collection)
|
||||
.collect::<std::vec::Vec<&crate::MetaplexTokenMetadataScenario>>();
|
||||
assert_eq!(collection.len(), 1);
|
||||
assert_eq!(collection[0].asset_family, crate::MetaplexTokenMetadataAssetFamily::Collection);
|
||||
assert_eq!(
|
||||
scenario.operation_codes[1],
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION
|
||||
);
|
||||
assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::NetworkSimulation);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user