Skip to content
Merged
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
169 changes: 168 additions & 1 deletion contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ impl GistRegistry {
mod tests {
use super::*;
use soroban_sdk::testutils::{Address as _, Events, Ledger};
use soroban_sdk::{Env, String};
use soroban_sdk::{Env, String, TryIntoVal, Val};

fn setup(env: &Env) -> GistRegistryClient<'_> {
let contract_id = env.register(GistRegistry, ());
Expand Down Expand Up @@ -883,4 +883,171 @@ mod tests {
assert!(!client.is_active(&id));
assert_eq!(client.list_gists_by_cell(&location, &0, &10).len(), 0);
}

// -- event emission ------------------------------------------------------
//
// Every state-changing entry point publishes exactly one contract event so
// the API layer and explorers can follow the registry. These tests pin down
// the topic and the payload of each one; a silent rename of a topic symbol
// breaks every downstream indexer, so the assertions are deliberately
// literal.

/// Decode the most recently published event as `(topic, data)`.
///
/// Works whether `env.events().all()` is cumulative or per-invocation,
/// because the interesting event is always the last one published.
fn last_event(env: &Env) -> (Symbol, Val) {
let events = env.events().all();
let (_, topics, data) = events.get(events.len() - 1).unwrap();
let topic = topics.get(0).unwrap().try_into_val(env).unwrap();
(topic, data)
}

/// `report_gist` publishes its payload as a `(gist_id, count)` pair.
fn decode_report_payload(env: &Env, data: Val) -> (u64, u32) {
let parts: Vec<Val> = data.try_into_val(env).unwrap();
(
parts.get(0).unwrap().try_into_val(env).unwrap(),
parts.get(1).unwrap().try_into_val(env).unwrap(),
)
}

// -- #1427: edit / delete ------------------------------------------------

#[test]
fn editing_a_gist_emits_gist_edited() {
let env = Env::default();
env.mock_all_auths();
let client = setup(&env);
client.initialize(&Address::generate(&env));

let author = Address::generate(&env);
let id = client.post_gist(
&Some(author),
&cell(&env, "r3gx"),
&cell(&env, "Qm1"),
&None,
);
let new_hash = cell(&env, "Qm2");
client.edit_gist(&id, &new_hash);

let (topic, data) = last_event(&env);
assert_eq!(topic, Symbol::new(&env, "gist_edited"));

// The payload is the full updated gist, so an indexer can pick up the
// new content pointer without a follow-up read.
let gist: Gist = data.try_into_val(&env).unwrap();
assert_eq!(gist.gist_id, id);
assert_eq!(gist.content_hash, new_hash);
}

#[test]
fn deleting_a_gist_emits_gist_deleted() {
let env = Env::default();
env.mock_all_auths();
let client = setup(&env);
client.initialize(&Address::generate(&env));

let author = Address::generate(&env);
let id = client.post_gist(
&Some(author),
&cell(&env, "r3gx"),
&cell(&env, "Qm1"),
&None,
);
client.delete_gist(&id);

let (topic, data) = last_event(&env);
assert_eq!(topic, Symbol::new(&env, "gist_deleted"));

let payload: u64 = data.try_into_val(&env).unwrap();
assert_eq!(payload, id);

// and the deletion itself is observable
assert!(client.get_gist(&id).is_none());
}

// -- #1428: hide / unhide / remove / report ------------------------------

#[test]
fn hiding_a_gist_emits_gist_hidden() {
let env = Env::default();
env.mock_all_auths();
let client = setup(&env);
client.initialize(&Address::generate(&env));

let id = client.post_gist(&None, &cell(&env, "r3gx"), &cell(&env, "Qm1"), &None);
client.hide_gist(&id);

let (topic, data) = last_event(&env);
assert_eq!(topic, Symbol::new(&env, "gist_hidden"));

let payload: u64 = data.try_into_val(&env).unwrap();
assert_eq!(payload, id);
}

#[test]
fn unhiding_a_gist_emits_gist_unhidden() {
let env = Env::default();
env.mock_all_auths();
let client = setup(&env);
client.initialize(&Address::generate(&env));

let id = client.post_gist(&None, &cell(&env, "r3gx"), &cell(&env, "Qm1"), &None);
client.hide_gist(&id);
client.unhide_gist(&id);

// the last of the two events is the unhide, not the hide
let (topic, data) = last_event(&env);
assert_eq!(topic, Symbol::new(&env, "gist_unhidden"));

let payload: u64 = data.try_into_val(&env).unwrap();
assert_eq!(payload, id);
}

#[test]
fn removing_a_gist_emits_gist_removed() {
let env = Env::default();
env.mock_all_auths();
let client = setup(&env);
client.initialize(&Address::generate(&env));

let id = client.post_gist(&None, &cell(&env, "r3gx"), &cell(&env, "Qm1"), &None);
client.remove_gist(&id);

let (topic, data) = last_event(&env);
assert_eq!(topic, Symbol::new(&env, "gist_removed"));

let payload: u64 = data.try_into_val(&env).unwrap();
assert_eq!(payload, id);

assert!(client.get_gist(&id).is_none());
}

#[test]
fn reporting_a_gist_emits_gist_reported_with_a_running_count() {
let env = Env::default();
env.mock_all_auths();
let client = setup(&env);
client.initialize(&Address::generate(&env));

let id = client.post_gist(&None, &cell(&env, "r3gx"), &cell(&env, "Qm1"), &None);

assert_eq!(client.report_gist(&id), 1);
let (topic, data) = last_event(&env);
assert_eq!(topic, Symbol::new(&env, "gist_reported"));
let (reported_id, count) = decode_report_payload(&env, data);
assert_eq!(reported_id, id);
assert_eq!(count, 1);

// A second report moves both the return value and the published count.
assert_eq!(client.report_gist(&id), 2);
let (topic, data) = last_event(&env);
assert_eq!(topic, Symbol::new(&env, "gist_reported"));
let (reported_id, count) = decode_report_payload(&env, data);
assert_eq!(reported_id, id);
assert_eq!(count, 2);

assert_eq!(client.report_count(&id), 2);
}
}
Loading