diff --git a/contracts/subscription/src/lib.rs b/contracts/subscription/src/lib.rs index e0656ca..870cf86 100644 --- a/contracts/subscription/src/lib.rs +++ b/contracts/subscription/src/lib.rs @@ -33,6 +33,8 @@ pub enum ContractError { InvalidServiceName = 10, SubscriptionExpired = 11, ServiceNotActive = 12, + InvalidRating = 13, + AlreadyRated = 14, } // --------------------------------------------------------------------------- @@ -48,29 +50,20 @@ pub enum DataKey { NextSubId, // Persistent storage Service(u64), - MerchantServices(Address), - Sub(u64), - SubscriberSubs(Address), - ServiceSubs(u64), - SubServicePair(Address, u64), +MerchantServices(Address), +Sub(u64), +SubscriberSubs(Address), +ServiceSubs(u64), +SubServicePair(Address, u64), + +ServiceRating(u64), +RatingGiven(Address, u64), } // --------------------------------------------------------------------------- // Domain types // --------------------------------------------------------------------------- -#[derive(Clone, PartialEq, Debug)] -#[contracttype] -pub struct Service { - pub service_id: u64, - pub merchant: Address, - pub name: String, - pub price: i128, - pub period_secs: u64, - pub trial_period_secs: u64, - pub approve_periods: u64, - pub is_active: bool, - pub created_at: u64, -} + #[derive(Clone, PartialEq, Debug)] #[contracttype] @@ -88,6 +81,13 @@ pub struct Subscription { pub created_at: u64, } +#[derive(Clone, PartialEq, Debug)] +#[contracttype] +pub struct ServiceRating { + pub total_score: u32, + pub total_raters: u32, +} + #[derive(Clone, PartialEq, Debug)] #[contracttype] pub struct ProcessResult { @@ -916,7 +916,65 @@ impl SubscriptionContract { env.events() .publish((symbol_short!("upgrade"),), new_wasm_hash); } +pub fn rate_service( + env: Env, + subscriber: Address, + service_id: u64, + score: u32, +) -> Result<(), ContractError> { + subscriber.require_auth(); + + // Ensure service exists + let svc_key = DataKey::Service(service_id); + if !env.storage().persistent().has(&svc_key) { + return Err(ContractError::ServiceNotFound); + } + + // Validate rating score + if score < 1 || score > 5 { + return Err(ContractError::InvalidRating); + } + + // Prevent duplicate rating + let rated_key = DataKey::RatingGiven(subscriber.clone(), service_id); + if env.storage().persistent().has(&rated_key) { + return Err(ContractError::AlreadyRated); + } + let rating_key = DataKey::ServiceRating(service_id); + + let mut rating: ServiceRating = env + .storage() + .persistent() + .get(&rating_key) + .unwrap_or(ServiceRating { + total_score: 0, + total_raters: 0, + }); + + rating.total_score += score; + rating.total_raters += 1; + + env.storage().persistent().set(&rating_key, &rating); + env.storage().persistent().set(&rated_key, &true); + + env.events().publish( + (symbol_short!("rate"), service_id), + (subscriber, score), + ); + + Ok(()) +} + +pub fn get_service_rating(env: Env, service_id: u64) -> ServiceRating { + env.storage() + .persistent() + .get(&DataKey::ServiceRating(service_id)) + .unwrap_or(ServiceRating { + total_score: 0, + total_raters: 0, + }) +} pub fn version(_env: Env) -> u32 { 1 } diff --git a/contracts/subscription/src/test.rs b/contracts/subscription/src/test.rs index ee269b2..d25b940 100644 --- a/contracts/subscription/src/test.rs +++ b/contracts/subscription/src/test.rs @@ -942,3 +942,75 @@ fn test_timestamp_overflow() { .try_subscribe(&s.subscriber, &svc.service_id, &true); assert_eq!(result, Err(Ok(ContractError::TimestampOverflow))); } + +#[test] +fn test_rate_service_success() { + let s = setup(); + let svc = register_default_service(&s); + + s.client.rate_service(&s.subscriber, &svc.service_id, &5); + + let rating = s.client.get_service_rating(&svc.service_id); + + assert_eq!(rating.total_score, 5); + assert_eq!(rating.total_raters, 1); + +} +#[test] +fn test_get_average_rating() { + let s = setup(); + let svc = register_default_service(&s); + + s.client.rate_service(&s.subscriber, &svc.service_id, &5); + s.client.rate_service(&s.subscriber2, &svc.service_id, &3); + + let avg = s.client.get_average_rating(&svc.service_id); + + assert_eq!(avg, 4); +} +#[test] +#[should_panic] +fn test_rate_service_duplicate_rejected() { + let s = setup(); + let svc = register_default_service(&s); + + s.client.rate_service(&s.subscriber, &svc.service_id, &5); + s.client.rate_service(&s.subscriber, &svc.service_id, &4); +} +#[test] +#[should_panic] +fn test_rate_service_invalid_score() { + let s = setup(); + let svc = register_default_service(&s); + + s.client.rate_service(&s.subscriber, &svc.service_id, &6); +} +#[test] +#[should_panic] +fn test_rate_service_not_found() { + let s = setup(); + + s.client.rate_service(&s.subscriber, &999, &5); +} +#[test] +fn test_rate_service_multiple_users() { + let s = setup(); + let svc = register_default_service(&s); + + s.client.rate_service(&s.subscriber, &svc.service_id, &5); + s.client.rate_service(&s.subscriber2, &svc.service_id, &3); + + let rating = s.client.get_service_rating(&svc.service_id); + + assert_eq!(rating.total_score, 8); + assert_eq!(rating.total_raters, 2); +} +#[test] +#[should_panic] +fn test_rate_service_requires_active_subscription() { + let s = setup(); + let svc = register_default_service(&s); + + // user belum subscribe, harus gagal + s.client.rate_service(&s.subscriber, &svc.service_id, &5); +}