From e9e4b95b46caff2355f8b1a9d14bbeaa1078975c Mon Sep 17 00:00:00 2001 From: Kapten boneng Date: Sat, 18 Apr 2026 00:34:40 +0700 Subject: [PATCH 1/6] Update lib.rs --- contracts/subscription/src/lib.rs | 58 +++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/contracts/subscription/src/lib.rs b/contracts/subscription/src/lib.rs index e0656ca..df6e3ae 100644 --- a/contracts/subscription/src/lib.rs +++ b/contracts/subscription/src/lib.rs @@ -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 } From 2248ea0633875581e473094b27a514f795f562ea Mon Sep 17 00:00:00 2001 From: Kapten boneng Date: Sat, 18 Apr 2026 01:21:33 +0700 Subject: [PATCH 2/6] Update lib.rs --- contracts/subscription/src/lib.rs | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/contracts/subscription/src/lib.rs b/contracts/subscription/src/lib.rs index df6e3ae..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 { From d1ebe745682d97707f48b6773a87c24abde3d6ac Mon Sep 17 00:00:00 2001 From: Kapten boneng Date: Sat, 18 Apr 2026 21:55:36 +0700 Subject: [PATCH 3/6] Update test.rs --- contracts/subscription/src/test.rs | 126 +++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/contracts/subscription/src/test.rs b/contracts/subscription/src/test.rs index ee269b2..4c3de5d 100644 --- a/contracts/subscription/src/test.rs +++ b/contracts/subscription/src/test.rs @@ -942,3 +942,129 @@ 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 env = Env::default(); + let contract_id = env.register(SubscriptionContract, ()); + let client = SubscriptionContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let merchant = Address::generate(&env); + let subscriber = Address::generate(&env); + let token = Address::generate(&env); + + client.__constructor(&admin, &token); + + let service = client.register_service( + &merchant, + &String::from_str(&env, "Premium Plan"), + &100, + &30, + &0, + &1, + ); + + client.rate_service(&subscriber, &service.service_id, &5); + + let rating = client.get_service_rating(&service.service_id); + + assert_eq!(rating.total_score, 5); + assert_eq!(rating.total_raters, 1); +} +#[test] +#[should_panic] +fn test_rate_service_duplicate_rejected() { + let env = Env::default(); + let contract_id = env.register(SubscriptionContract, ()); + let client = SubscriptionContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let merchant = Address::generate(&env); + let subscriber = Address::generate(&env); + let token = Address::generate(&env); + + client.__constructor(&admin, &token); + + let service = client.register_service( + &merchant, + &String::from_str(&env, "Premium Plan"), + &100, + &30, + &0, + &1, + ); + + client.rate_service(&subscriber, &service.service_id, &5); + client.rate_service(&subscriber, &service.service_id, &4); +} +#[test] +#[should_panic] +fn test_rate_service_invalid_score() { + let env = Env::default(); + let contract_id = env.register(SubscriptionContract, ()); + let client = SubscriptionContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let merchant = Address::generate(&env); + let subscriber = Address::generate(&env); + let token = Address::generate(&env); + + client.__constructor(&admin, &token); + + let service = client.register_service( + &merchant, + &String::from_str(&env, "Premium Plan"), + &100, + &30, + &0, + &1, + ); + + client.rate_service(&subscriber, &service.service_id, &6); +} +#[test] +#[should_panic] +fn test_rate_service_not_found() { + let env = Env::default(); + let contract_id = env.register(SubscriptionContract, ()); + let client = SubscriptionContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let subscriber = Address::generate(&env); + let token = Address::generate(&env); + + client.__constructor(&admin, &token); + + client.rate_service(&subscriber, &999, &5); +} +#[test] +fn test_rate_service_multiple_users() { + let env = Env::default(); + let contract_id = env.register(SubscriptionContract, ()); + let client = SubscriptionContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let merchant = Address::generate(&env); + let user1 = Address::generate(&env); + let user2 = Address::generate(&env); + let token = Address::generate(&env); + + client.__constructor(&admin, &token); + + let service = client.register_service( + &merchant, + &String::from_str(&env, "Premium Plan"), + &100, + &30, + &0, + &1, + ); + + client.rate_service(&user1, &service.service_id, &5); + client.rate_service(&user2, &service.service_id, &3); + + let rating = client.get_service_rating(&service.service_id); + + assert_eq!(rating.total_score, 8); + assert_eq!(rating.total_raters, 2); +} From f7ddf13decc8d64f3338832d39540fda9b3d50e8 Mon Sep 17 00:00:00 2001 From: Kapten boneng Date: Sat, 18 Apr 2026 22:06:45 +0700 Subject: [PATCH 4/6] Update test.rs --- contracts/subscription/src/test.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contracts/subscription/src/test.rs b/contracts/subscription/src/test.rs index 4c3de5d..af0d864 100644 --- a/contracts/subscription/src/test.rs +++ b/contracts/subscription/src/test.rs @@ -972,6 +972,18 @@ fn test_rate_service_success() { 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 env = Env::default(); From d1640f6b43b0e52bb8250e7cc9b4234109bf2ef7 Mon Sep 17 00:00:00 2001 From: Kapten boneng Date: Sat, 18 Apr 2026 22:37:17 +0700 Subject: [PATCH 5/6] Add tests for service rating system --- contracts/subscription/src/test.rs | 115 +++++------------------------ 1 file changed, 20 insertions(+), 95 deletions(-) diff --git a/contracts/subscription/src/test.rs b/contracts/subscription/src/test.rs index af0d864..ca59d41 100644 --- a/contracts/subscription/src/test.rs +++ b/contracts/subscription/src/test.rs @@ -942,34 +942,19 @@ 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 env = Env::default(); - let contract_id = env.register(SubscriptionContract, ()); - let client = SubscriptionContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let merchant = Address::generate(&env); - let subscriber = Address::generate(&env); - let token = Address::generate(&env); - - client.__constructor(&admin, &token); - - let service = client.register_service( - &merchant, - &String::from_str(&env, "Premium Plan"), - &100, - &30, - &0, - &1, - ); + let s = setup(); + let svc = register_default_service(&s); - client.rate_service(&subscriber, &service.service_id, &5); + s.client.rate_service(&s.subscriber, &svc.service_id, &5); - let rating = client.get_service_rating(&service.service_id); + 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() { @@ -986,96 +971,36 @@ fn test_get_average_rating() { #[test] #[should_panic] fn test_rate_service_duplicate_rejected() { - let env = Env::default(); - let contract_id = env.register(SubscriptionContract, ()); - let client = SubscriptionContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let merchant = Address::generate(&env); - let subscriber = Address::generate(&env); - let token = Address::generate(&env); - - client.__constructor(&admin, &token); - - let service = client.register_service( - &merchant, - &String::from_str(&env, "Premium Plan"), - &100, - &30, - &0, - &1, - ); + let s = setup(); + let svc = register_default_service(&s); - client.rate_service(&subscriber, &service.service_id, &5); - client.rate_service(&subscriber, &service.service_id, &4); + 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 env = Env::default(); - let contract_id = env.register(SubscriptionContract, ()); - let client = SubscriptionContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let merchant = Address::generate(&env); - let subscriber = Address::generate(&env); - let token = Address::generate(&env); - - client.__constructor(&admin, &token); - - let service = client.register_service( - &merchant, - &String::from_str(&env, "Premium Plan"), - &100, - &30, - &0, - &1, - ); + let s = setup(); + let svc = register_default_service(&s); - client.rate_service(&subscriber, &service.service_id, &6); + s.client.rate_service(&s.subscriber, &svc.service_id, &6); } #[test] #[should_panic] fn test_rate_service_not_found() { - let env = Env::default(); - let contract_id = env.register(SubscriptionContract, ()); - let client = SubscriptionContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let subscriber = Address::generate(&env); - let token = Address::generate(&env); - - client.__constructor(&admin, &token); + let s = setup(); - client.rate_service(&subscriber, &999, &5); + s.client.rate_service(&s.subscriber, &999, &5); } #[test] fn test_rate_service_multiple_users() { - let env = Env::default(); - let contract_id = env.register(SubscriptionContract, ()); - let client = SubscriptionContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let merchant = Address::generate(&env); - let user1 = Address::generate(&env); - let user2 = Address::generate(&env); - let token = Address::generate(&env); - - client.__constructor(&admin, &token); - - let service = client.register_service( - &merchant, - &String::from_str(&env, "Premium Plan"), - &100, - &30, - &0, - &1, - ); + let s = setup(); + let svc = register_default_service(&s); - client.rate_service(&user1, &service.service_id, &5); - client.rate_service(&user2, &service.service_id, &3); + s.client.rate_service(&s.subscriber, &svc.service_id, &5); + s.client.rate_service(&s.subscriber2, &svc.service_id, &3); - let rating = client.get_service_rating(&service.service_id); + let rating = s.client.get_service_rating(&svc.service_id); assert_eq!(rating.total_score, 8); assert_eq!(rating.total_raters, 2); From a8fbdbf4f7f100183b69888e6ec6a5e8b3cea8e9 Mon Sep 17 00:00:00 2001 From: Kapten boneng Date: Sat, 18 Apr 2026 23:06:58 +0700 Subject: [PATCH 6/6] Update test.rs --- contracts/subscription/src/test.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/subscription/src/test.rs b/contracts/subscription/src/test.rs index ca59d41..d25b940 100644 --- a/contracts/subscription/src/test.rs +++ b/contracts/subscription/src/test.rs @@ -1005,3 +1005,12 @@ fn test_rate_service_multiple_users() { 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); +}