Skip to content

Commit 4d24008

Browse files
authored
Adds missing fields to subscribers (#780)
* Add an integration test for listing subscribers * Add missing fields to `wp_com::subscribers::Subscriber` * Fix parameter names for `ListSubscribersSortField` and add tests * Use the correct output type for `/sites/<wp_com_site_id>/subscribers/individual` * Add `SubscriberCountry` * Use SubscriptionId & UserId for GetSubscriberQuery * Add retrieve subscriber tests * Move wp_com test credentials to wp_com_test_credentials.json file * Ignore subscriber tests * Update subscriber unit tests for `GetSubscriberQuery` changes * Remove useless import in subscriber tests * Add `plans` field to subscribers
1 parent 8032ee7 commit 4d24008

File tree

8 files changed

+224
-22
lines changed

8 files changed

+224
-22
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ test_credentials.json
4444
/logs
4545
dump.sql
4646

47+
# WP_COM Testing
48+
wp_com_test_credentials.json
49+
4750
# CI Cache
4851
.cargo/*
4952
/vendor

wp_api/src/wp_com/endpoint/subscribers.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use crate::{
44
WpComNamespace, WpComSiteId,
55
subscribers::{
66
AddSubscribersParams, AddSubscribersResponse, GetSubscriberQuery,
7-
ListSubscribersResponse, SubscriberImportJob, SubscriberImportJobsListParams,
8-
SubscriberStatsResponse, SubscribersListParams, UploadId,
7+
ListSubscribersResponse, Subscriber, SubscriberImportJob,
8+
SubscriberImportJobsListParams, SubscriberStatsResponse, SubscribersListParams,
9+
UploadId,
910
},
1011
},
1112
};
@@ -15,7 +16,7 @@ use wp_derive_request_builder::WpDerivedRequest;
1516
enum SubscribersRequest {
1617
#[get(url = "/sites/<wp_com_site_id>/subscribers", params = &SubscribersListParams, output = ListSubscribersResponse)]
1718
ListSubscribers,
18-
#[get(url = "/sites/<wp_com_site_id>/subscribers/individual", params = &GetSubscriberQuery, output = SubscriberImportJob)]
19+
#[get(url = "/sites/<wp_com_site_id>/subscribers/individual", params = &GetSubscriberQuery, output = Subscriber)]
1920
GetSubscriber,
2021
#[get(url = "/sites/<wp_com_site_id>/subscribers/import", params = &SubscriberImportJobsListParams, output = Vec<SubscriberImportJob>)]
2122
ListSubscriberImportJobs,

wp_api/src/wp_com/subscribers.rs

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ use wp_serde_helper::deserialize_u64_or_string;
1313
#[derive(Debug, Serialize, Deserialize, uniffi::Record)]
1414
pub struct Subscriber {
1515
pub user_id: UserId,
16+
pub subscription_id: SubscriptionId,
1617
pub display_name: String,
1718
pub email_address: String,
19+
pub is_email_subscriber: bool,
1820
pub email_subscription_id: Option<u64>,
1921
pub date_subscribed: WpGmtDateTime,
20-
pub subscription_status: String,
22+
pub subscription_status: Option<String>,
2123
pub avatar: String,
24+
pub url: Option<String>,
25+
pub country: Option<SubscriberCountry>,
26+
pub plans: Option<Vec<SubscriptionPlan>>,
2227
}
2328

2429
#[derive(
@@ -69,6 +74,27 @@ pub enum SubscriptionStatus {
6974
Custom(String),
7075
}
7176

77+
#[derive(Debug, Serialize, Deserialize, uniffi::Record)]
78+
pub struct SubscriberCountry {
79+
code: String,
80+
name: String,
81+
}
82+
83+
#[derive(Debug, Serialize, Deserialize, uniffi::Record)]
84+
pub struct SubscriptionPlan {
85+
pub is_gift: bool,
86+
pub gift_id: Option<u64>,
87+
pub paid_subscription_id: Option<String>,
88+
pub status: String,
89+
pub title: String,
90+
pub currency: String,
91+
pub renew_interval: String,
92+
pub inactive_renew_interval: Option<String>,
93+
pub renewal_price: f64,
94+
pub start_date: WpGmtDateTime,
95+
pub end_date: WpGmtDateTime,
96+
}
97+
7298
#[derive(Debug, Default, PartialEq, Eq, uniffi::Record)]
7399
pub struct SubscribersListParams {
74100
// The current page.
@@ -131,7 +157,9 @@ impl AppendUrlQueryPairs for SubscribersListParams {
131157
#[strum(serialize_all = "snake_case")]
132158
pub enum ListSubscribersSortField {
133159
DateSubscribed,
160+
#[strum(serialize = "email")]
134161
EmailAddress,
162+
#[strum(serialize = "name")]
135163
DisplayName,
136164
Plan,
137165
SubscriptionStatus,
@@ -153,10 +181,10 @@ pub struct ListSubscribersResponse {
153181
#[derive(Debug, uniffi::Enum)]
154182
pub enum GetSubscriberQuery {
155183
// Return subscribers that receive notifications via WordPress.com for new posts.
156-
WpCom(u64),
184+
WpCom(UserId),
157185

158186
// Return subscribers that receive notifications via email for new posts.
159-
Email(u64),
187+
Email(SubscriptionId),
160188
}
161189

162190
impl AppendUrlQueryPairs for GetSubscriberQuery {
@@ -270,6 +298,25 @@ impl AppendUrlQueryPairs for SubscriberImportJobsListParams {
270298
}
271299
}
272300

301+
impl_as_query_value_for_new_type!(SubscriptionId);
302+
uniffi::custom_newtype!(SubscriptionId, u64);
303+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
304+
pub struct SubscriptionId(pub u64);
305+
306+
impl std::str::FromStr for SubscriptionId {
307+
type Err = std::num::ParseIntError;
308+
309+
fn from_str(s: &str) -> Result<Self, Self::Err> {
310+
s.parse().map(Self)
311+
}
312+
}
313+
314+
impl std::fmt::Display for SubscriptionId {
315+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316+
write!(f, "{}", self.0)
317+
}
318+
}
319+
273320
impl_as_query_value_for_new_type!(UploadId);
274321
uniffi::custom_newtype!(UploadId, u64);
275322
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -398,7 +445,7 @@ mod tests {
398445
.expect("Failed to parse url");
399446

400447
let mut query_pairs = url.query_pairs_mut();
401-
GetSubscriberQuery::WpCom(123).append_query_pairs(&mut query_pairs);
448+
GetSubscriberQuery::WpCom(UserId(123)).append_query_pairs(&mut query_pairs);
402449
assert_eq!(
403450
query_pairs.finish().as_str(),
404451
"https://public-api.wordpress.com/wpcom/v2/sites/1234/subscribers/individual?user_id=123&type=wpcom"
@@ -413,7 +460,7 @@ mod tests {
413460
.expect("Failed to parse url");
414461

415462
let mut query_pairs = url.query_pairs_mut();
416-
GetSubscriberQuery::Email(123).append_query_pairs(&mut query_pairs);
463+
GetSubscriberQuery::Email(SubscriptionId(123)).append_query_pairs(&mut query_pairs);
417464
assert_eq!(
418465
query_pairs.finish().as_str(),
419466
"https://public-api.wordpress.com/wpcom/v2/sites/1234/subscribers/individual?subscription_id=123&type=email"
@@ -508,4 +555,15 @@ mod tests {
508555
assert_eq!(response.counts.email_subscribers, 26);
509556
assert_eq!(response.aggregate.len(), 60);
510557
}
558+
559+
#[test]
560+
fn test_get_subscriber_with_paid_plans_response_deserialization() {
561+
let json_file_path = "tests/wpcom/subscribers/subscriber-with-paid-plans.json";
562+
let file = std::fs::File::open(json_file_path).expect("Failed to open file");
563+
let subscriber: Subscriber = serde_json::from_reader(file).expect("Unable to parse JSON");
564+
565+
let plans = subscriber.plans.expect("JSON file includes plans");
566+
assert_eq!(plans.len(), 2);
567+
assert_eq!(plans[0].start_date.to_string(), "2025-01-13T18:51:55+00:00");
568+
}
511569
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"user_id": 123,
3+
"subscription_id": 123,
4+
"email_address": "[email protected]",
5+
"date_subscribed": "2025-04-17T14:40:00+00:00",
6+
"is_email_subscriber": false,
7+
"subscription_status": "Subscribed",
8+
"avatar": "https://example.com/avatar",
9+
"display_name": "Foo",
10+
"url": "http://example.wordpress.com",
11+
"country": {
12+
"code": "US",
13+
"name": "United States"
14+
},
15+
"plans": [
16+
{
17+
"is_gift": false,
18+
"gift_id": null,
19+
"paid_subscription_id": "12422686",
20+
"status": "active",
21+
"title": "Newsletter Tier",
22+
"currency": "USD",
23+
"renew_interval": "1 month",
24+
"inactive_renew_interval": null,
25+
"renewal_price": 0.5,
26+
"start_date": "2025-01-13T18:51:55+00:00",
27+
"end_date": "2025-02-13T18:51:55+00:00"
28+
},
29+
{
30+
"is_gift": true,
31+
"gift_id": 31,
32+
"paid_subscription_id": null,
33+
"status": "active",
34+
"title": "Newsletter Tier 3",
35+
"currency": "USD",
36+
"renew_interval": "one-time",
37+
"inactive_renew_interval": null,
38+
"renewal_price": 0,
39+
"start_date": "2025-05-08T14:50:28+00:00",
40+
"end_date": "2025-06-07T14:50:28+00:00"
41+
}
42+
]
43+
}

wp_api_integration_tests/build.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,49 @@ use std::{
77
};
88

99
fn main() -> Result<(), Box<dyn Error>> {
10-
generate_test_credentials_file()
11-
}
12-
13-
fn generate_test_credentials_file() -> Result<(), Box<dyn Error>> {
14-
// Tell Cargo to rerun if the test credentials file changes
15-
println!("cargo::rerun-if-changed=../test_credentials");
16-
1710
let out_dir = env::var("OUT_DIR")?;
1811
let dest_path = Path::new(&out_dir).join("generated_test_credentials.rs");
1912
let mut buf_writer = BufWriter::new(File::create(dest_path)?);
2013

21-
let instance = if let Ok(file) = fs::File::open("../test_credentials.json") {
14+
generate_test_credentials("test_credentials", "TestCredentials", &mut buf_writer)?;
15+
generate_test_credentials(
16+
"wp_com_test_credentials",
17+
"WpComTestCredentials",
18+
&mut buf_writer,
19+
)
20+
}
21+
22+
fn generate_test_credentials(
23+
file_name: &str,
24+
test_credentials_type: &str,
25+
buf_writer: &mut BufWriter<File>,
26+
) -> Result<(), Box<dyn Error>> {
27+
let test_credential_json_file_path = format!("../{file_name}.json");
28+
// Tell Cargo to rerun if the test credentials file changes
29+
println!("cargo::rerun-if-changed={test_credential_json_file_path}");
30+
31+
let instance = if let Ok(file) = fs::File::open(&test_credential_json_file_path) {
2232
let fields = serde_json::from_reader::<File, serde_json::Value>(file)
23-
.expect("test_credentials.json should be a valid JSON file")
33+
.expect("{test_credential_json_file_path} should be a valid JSON file")
2434
.as_object()
25-
.expect("test_credentials.json should be a valid JSON Object")
35+
.expect("{test_credential_json_file_path} should be a valid JSON Object")
2636
.into_iter()
2737
.map(|(k, v)| format!("{}: {},", k, v))
2838
.collect::<Vec<String>>()
2939
.join("\n");
30-
format!("TestCredentials {{ {} }}", fields)
40+
format!("{test_credentials_type} {{ {} }}", fields)
3141
} else {
32-
"TestCredentials::default()".to_string()
42+
format!("{test_credentials_type}::default()")
3343
};
3444
let generated_content = format!(
3545
r#"
36-
impl TestCredentials {{
46+
impl {} {{
3747
pub fn instance() -> Self {{
3848
{}
3949
}}
4050
}}
4151
"#,
42-
instance
52+
test_credentials_type, instance
4353
);
4454

4555
write!(buf_writer, "{}", generated_content)?;

wp_api_integration_tests/src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::prelude::*;
2+
use wp_api::wp_com::client::WpComApiClient;
23

34
pub mod mock;
45
pub mod prelude;
@@ -35,6 +36,14 @@ impl TestCredentials {
3536
}
3637
}
3738

39+
#[derive(Debug, Default)]
40+
pub struct WpComTestCredentials {
41+
pub bearer_token: &'static str,
42+
pub site_id: u64,
43+
pub wp_com_subscriber_user_id: i64,
44+
pub email_subscriber_subscription_id: u64,
45+
}
46+
3847
pub mod backend;
3948

4049
// The first user is also the current user
@@ -128,6 +137,18 @@ pub fn api_client_with_auth_provider(auth_provider: Arc<WpAuthenticationProvider
128137
)
129138
}
130139

140+
pub fn wp_com_client() -> WpComApiClient {
141+
WpComApiClient::new(WpApiClientDelegate {
142+
auth_provider: WpAuthenticationProvider::static_with_auth(WpAuthentication::Bearer {
143+
token: WpComTestCredentials::instance().bearer_token.to_string(),
144+
})
145+
.into(),
146+
request_executor: Arc::new(ReqwestRequestExecutor::default()),
147+
middleware_pipeline: Arc::new(WpApiMiddlewarePipeline::default()),
148+
app_notifier: Arc::new(EmptyAppNotifier),
149+
})
150+
}
151+
131152
pub fn test_site_url() -> ParsedUrl {
132153
let mut url: Url = TestCredentials::instance()
133154
.site_url
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use wp_api::wp_com::{
2+
WpComSiteId,
3+
subscribers::{
4+
GetSubscriberQuery, ListSubscribersSortField, SubscribersListParams, SubscriptionId,
5+
},
6+
};
7+
use wp_api_integration_tests::{WpComTestCredentials, prelude::*, wp_com_client};
8+
9+
#[tokio::test]
10+
#[apply(list_cases)]
11+
#[parallel]
12+
#[ignore]
13+
async fn list_subscribers(#[case] params: SubscribersListParams) {
14+
let subscribers = wp_com_client()
15+
.subscribers()
16+
.list_subscribers(
17+
&WpComSiteId(WpComTestCredentials::instance().site_id),
18+
&params,
19+
)
20+
.await
21+
.assert_response();
22+
assert!(
23+
subscribers.data.total > 0,
24+
"Retrieved no subscribers: {:#?}",
25+
subscribers
26+
);
27+
}
28+
29+
#[tokio::test]
30+
#[apply(retrieve_cases)]
31+
#[parallel]
32+
#[ignore]
33+
async fn retrieve_subscriber(#[case] query: GetSubscriberQuery) {
34+
wp_com_client()
35+
.subscribers()
36+
.get_subscriber(
37+
&WpComSiteId(WpComTestCredentials::instance().site_id),
38+
&query,
39+
)
40+
.await
41+
.assert_response();
42+
}
43+
44+
#[template]
45+
#[rstest]
46+
#[case::default(SubscribersListParams::default())]
47+
#[case::sort_date_subscribed(generate!(SubscribersListParams, (sort, Some(ListSubscribersSortField::DateSubscribed))))]
48+
#[case::sort_email_address(generate!(SubscribersListParams, (sort, Some(ListSubscribersSortField::EmailAddress))))]
49+
#[case::sort_display_name(generate!(SubscribersListParams, (sort, Some(ListSubscribersSortField::DisplayName))))]
50+
#[case::sort_plan(generate!(SubscribersListParams, (sort, Some(ListSubscribersSortField::Plan))))]
51+
#[case::sort_subscription_status(generate!(SubscribersListParams, (sort, Some(ListSubscribersSortField::SubscriptionStatus))))]
52+
pub fn list_cases(#[case] params: SubscribersListParams) {}
53+
54+
#[template]
55+
#[rstest]
56+
#[case::wp_com_subscriber(GetSubscriberQuery::WpCom(UserId(WpComTestCredentials::instance().wp_com_subscriber_user_id)))]
57+
#[case::email_subscriber(GetSubscriberQuery::Email(SubscriptionId(
58+
WpComTestCredentials::instance().email_subscriber_subscription_id
59+
)))]
60+
pub fn retrieve_cases(#[case] query: GetSubscriberQuery) {}

wp_com_test_credentials.json-example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"bearer_token": "replace_with_your_oauth2_token",
3+
"site_id": 0,
4+
"wp_com_subscriber_user_id": 0,
5+
"email_subscriber_subscription_id": 0
6+
}

0 commit comments

Comments
 (0)