Skip to content

Commit bf72300

Browse files
authored
Merge pull request #6310 from Turbo87/token-scopes-in-list
GET /me/tokens: Add `crate_scopes` and `endpoint_scopes` fields
2 parents 7eb55a7 + ad8d6c3 commit bf72300

File tree

4 files changed

+92
-43
lines changed

4 files changed

+92
-43
lines changed

src/models/token.rs

-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ pub struct ApiToken {
2727
#[serde(skip)]
2828
pub revoked: bool,
2929
/// `None` or a list of crate scope patterns (see RFC #2947)
30-
#[serde(skip)]
3130
pub crate_scopes: Option<Vec<CrateScope>>,
3231
/// A list of endpoint scopes or `None` for the `legacy` endpoint scope (see RFC #2947)
33-
#[serde(skip)]
3432
pub endpoint_scopes: Option<Vec<EndpointScope>>,
3533
}
3634

src/models/token/scopes.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use diesel::serialize::{self, IsNull, Output, ToSql};
55
use diesel::sql_types::Text;
66
use std::io::Write;
77

8-
#[derive(Clone, Copy, Debug, PartialEq, Eq, AsExpression)]
8+
#[derive(Clone, Copy, Debug, PartialEq, Eq, AsExpression, Serialize)]
99
#[diesel(sql_type = Text)]
10+
#[serde(rename_all = "kebab-case")]
1011
pub enum EndpointScope {
1112
PublishNew,
1213
PublishUpdate,
@@ -53,7 +54,8 @@ impl FromSql<Text, Pg> for EndpointScope {
5354
}
5455
}
5556

56-
#[derive(Clone, Debug, PartialEq, Eq)]
57+
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
58+
#[serde(transparent)]
5759
pub struct CrateScope {
5860
pattern: String,
5961
}
@@ -125,6 +127,34 @@ impl CrateScope {
125127
mod tests {
126128
use super::*;
127129

130+
#[test]
131+
fn endpoint_scope_serialization() {
132+
fn assert(scope: EndpointScope, expected: &str) {
133+
assert_ok_eq!(serde_json::to_string(&scope), expected);
134+
}
135+
136+
assert(EndpointScope::ChangeOwners, "\"change-owners\"");
137+
assert(EndpointScope::PublishNew, "\"publish-new\"");
138+
assert(EndpointScope::PublishUpdate, "\"publish-update\"");
139+
assert(EndpointScope::Yank, "\"yank\"");
140+
}
141+
142+
#[test]
143+
fn crate_scope_serialization() {
144+
fn assert(scope: &str, expected: &str) {
145+
let scope = assert_ok!(CrateScope::try_from(scope));
146+
assert_ok_eq!(serde_json::to_string(&scope), expected);
147+
}
148+
149+
assert("foo", "\"foo\"");
150+
assert("foo*", "\"foo*\"");
151+
assert("f*", "\"f*\"");
152+
assert("*", "\"*\"");
153+
assert("foo-bar", "\"foo-bar\"");
154+
assert("foo_bar", "\"foo_bar\"");
155+
assert("FooBar", "\"FooBar\"");
156+
}
157+
128158
#[test]
129159
fn crate_scope_validation() {
130160
assert_ok!(CrateScope::try_from("foo"));

src/tests/routes/me/tokens/list.rs

+39-39
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
use crate::routes::me::tokens::delete::RevokedResponse;
1+
use crate::util::insta::{self, assert_yaml_snapshot};
22
use crate::util::{RequestHelper, TestApp};
3+
use cargo_registry::models::token::{CrateScope, EndpointScope};
34
use cargo_registry::models::ApiToken;
4-
use std::collections::HashSet;
5-
6-
#[derive(Deserialize)]
7-
struct DecodableApiToken {
8-
name: String,
9-
}
10-
11-
#[derive(Deserialize)]
12-
struct ListResponse {
13-
api_tokens: Vec<DecodableApiToken>,
14-
}
5+
use http::StatusCode;
156

167
#[test]
178
fn list_logged_out() {
@@ -28,33 +19,40 @@ fn list_with_api_token_is_forbidden() {
2819
#[test]
2920
fn list_empty() {
3021
let (_, _, user) = TestApp::init().with_user();
31-
let json: ListResponse = user.get("/api/v1/me/tokens").good();
32-
assert_eq!(json.api_tokens.len(), 0);
22+
let response = user.get::<()>("/api/v1/me/tokens");
23+
assert_eq!(response.status(), StatusCode::OK);
24+
let json = response.into_json();
25+
let response_tokens = json["api_tokens"].as_array().unwrap();
26+
assert_eq!(response_tokens.len(), 0);
3327
}
3428

3529
#[test]
3630
fn list_tokens() {
3731
let (app, _, user) = TestApp::init().with_user();
3832
let id = user.as_model().id;
39-
let tokens = app.db(|conn| {
33+
app.db(|conn| {
4034
vec![
4135
assert_ok!(ApiToken::insert(conn, id, "bar")),
42-
assert_ok!(ApiToken::insert(conn, id, "baz")),
36+
assert_ok!(ApiToken::insert_with_scopes(
37+
conn,
38+
id,
39+
"baz",
40+
Some(vec![
41+
CrateScope::try_from("serde").unwrap(),
42+
CrateScope::try_from("serde-*").unwrap()
43+
]),
44+
Some(vec![EndpointScope::PublishUpdate])
45+
)),
4346
]
4447
});
4548

46-
let json: ListResponse = user.get("/api/v1/me/tokens").good();
47-
assert_eq!(json.api_tokens.len(), tokens.len());
48-
assert_eq!(
49-
json.api_tokens
50-
.into_iter()
51-
.map(|t| t.name)
52-
.collect::<HashSet<_>>(),
53-
tokens
54-
.into_iter()
55-
.map(|t| t.model.name)
56-
.collect::<HashSet<_>>()
57-
);
49+
let response = user.get::<()>("/api/v1/me/tokens");
50+
assert_eq!(response.status(), StatusCode::OK);
51+
assert_yaml_snapshot!(response.into_json(), {
52+
".api_tokens[].id" => insta::any_id_redaction(),
53+
".api_tokens[].created_at" => "[datetime]",
54+
".api_tokens[].last_used_at" => "[datetime]",
55+
});
5856
}
5957

6058
#[test]
@@ -69,19 +67,21 @@ fn list_tokens_exclude_revoked() {
6967
});
7068

7169
// List tokens expecting them all to be there.
72-
let json: ListResponse = user.get("/api/v1/me/tokens").good();
73-
assert_eq!(json.api_tokens.len(), tokens.len());
70+
let response = user.get::<()>("/api/v1/me/tokens");
71+
assert_eq!(response.status(), StatusCode::OK);
72+
let json = response.into_json();
73+
let response_tokens = json["api_tokens"].as_array().unwrap();
74+
assert_eq!(response_tokens.len(), 2);
7475

7576
// Revoke the first token.
76-
let _json: RevokedResponse = user
77-
.delete(&format!("/api/v1/me/tokens/{}", tokens[0].model.id))
78-
.good();
77+
let response = user.delete::<()>(&format!("/api/v1/me/tokens/{}", tokens[0].model.id));
78+
assert_eq!(response.status(), StatusCode::OK);
7979

8080
// Check that we now have one less token being listed.
81-
let json: ListResponse = user.get("/api/v1/me/tokens").good();
82-
assert_eq!(json.api_tokens.len(), tokens.len() - 1);
83-
assert!(!json
84-
.api_tokens
85-
.iter()
86-
.any(|token| token.name == tokens[0].model.name));
81+
let response = user.get::<()>("/api/v1/me/tokens");
82+
assert_eq!(response.status(), StatusCode::OK);
83+
let json = response.into_json();
84+
let response_tokens = json["api_tokens"].as_array().unwrap();
85+
assert_eq!(response_tokens.len(), 1);
86+
assert_eq!(response_tokens[0]["name"], json!("baz"));
8787
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: src/tests/routes/me/tokens/list.rs
3+
expression: response.into_json()
4+
---
5+
api_tokens:
6+
- crate_scopes: ~
7+
created_at: "[datetime]"
8+
endpoint_scopes: ~
9+
id: "[id]"
10+
last_used_at: "[datetime]"
11+
name: bar
12+
- crate_scopes:
13+
- serde
14+
- serde-*
15+
created_at: "[datetime]"
16+
endpoint_scopes:
17+
- publish-update
18+
id: "[id]"
19+
last_used_at: "[datetime]"
20+
name: baz
21+

0 commit comments

Comments
 (0)