Skip to content

Commit b11fe16

Browse files
committed
tests pass
1 parent f90a827 commit b11fe16

7 files changed

Lines changed: 124 additions & 84 deletions

File tree

crates/crates_io_database/src/models/user.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ impl User {
4242
pub async fn find_by_login(mut conn: &AsyncPgConnection, login: &str) -> QueryResult<User> {
4343
User::query()
4444
.filter(lower(users::gh_login).eq(login.to_lowercase()))
45+
// This ordering will be unnecessary when we switch to crates.io usernames that
46+
// are unique.
47+
.order(users::id.desc())
4548
.first(&mut conn)
4649
.await
4750
}
@@ -185,6 +188,7 @@ impl OauthGithubUpdate<'_> {
185188
diesel::insert_into(oauth_github::table)
186189
.values((
187190
oauth_github::user_id.eq(user_id),
191+
oauth_github::account_id.eq(self.account_id),
188192
oauth_github::encrypted_token.eq(self.encrypted_token),
189193
oauth_github::login.eq(self.login),
190194
oauth_github::avatar.eq(self.avatar),

crates/crates_io_database_dump/src/dump-db.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,15 +254,10 @@ id in (
254254
id = "public"
255255
gh_login = "public"
256256
name = "public"
257-
gh_avatar = "public"
258-
gh_id = "public"
259257
account_lock_reason = "private"
260258
account_lock_until = "private"
261259
is_admin = "private"
262260
publish_notifications = "private"
263-
gh_encrypted_token = "private"
264-
[users.column_defaults]
265-
gh_encrypted_token = "''"
266261

267262
[version_downloads]
268263
dependencies = ["versions"]

src/controllers/session.rs

Lines changed: 76 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -156,44 +156,53 @@ pub async fn save_user_to_database(
156156
.maybe_avatar(gh_user.avatar_url.as_deref())
157157
.build();
158158

159-
// should start a transaction here
160-
161-
match oauth_github_update.update(conn).await {
162-
// oauth github exists, which means user must too
163-
Ok(oauth_github) => {
164-
let user = User::find(conn, oauth_github.user_id).await?;
165-
166-
// update user display name; eventually we should stop syncing this with github
167-
diesel::update(users::table)
168-
.filter(users::id.eq(user.id))
169-
.set(users::name.eq(gh_user.name.as_ref()))
170-
.execute(conn)
171-
.await?;
172-
173-
Ok(user.id)
174-
}
175-
// oauth github does not exist, which means user must not either and we need to create both
176-
Err(diesel::result::Error::NotFound) => {
177-
let new_user = NewUser::builder()
178-
.gh_login(&gh_user.login)
179-
.maybe_name(gh_user.name.as_deref())
180-
.build();
181-
182-
let user_id =
183-
create_or_update_user(&new_user, gh_user.email.as_deref(), emails, conn).await?;
184-
185-
oauth_github_update.insert(conn, user_id).await?;
186-
187-
Ok(user_id)
188-
}
189-
Err(error) if is_read_only_error(&error) => {
190-
// If we're in read only mode, we can't update their details
191-
// just look for an existing user
192-
find_user_by_gh_id(conn, gh_user.id).await?.ok_or(error)
159+
conn.transaction(async |conn| {
160+
match dbg!(oauth_github_update.update(conn).await) {
161+
// oauth github exists, which means user must too
162+
Ok(oauth_github) => {
163+
let user = User::find(conn, oauth_github.user_id).await?;
164+
dbg!(&user);
165+
166+
// update user display name and gh_login; eventually we should stop syncing these
167+
// with github
168+
diesel::update(users::table)
169+
.filter(users::id.eq(user.id))
170+
.set((
171+
users::name.eq(gh_user.name.as_ref()),
172+
users::gh_login.eq(&gh_user.login),
173+
))
174+
.execute(conn)
175+
.await?;
176+
177+
Ok(user.id)
178+
}
179+
// oauth github does not exist, which means user must not either and we need to create both
180+
Err(diesel::result::Error::NotFound) => {
181+
let new_user = NewUser::builder()
182+
.gh_login(&gh_user.login)
183+
.maybe_name(gh_user.name.as_deref())
184+
.build();
185+
dbg!(&new_user);
186+
187+
let user_id =
188+
create_or_update_user(&new_user, gh_user.email.as_deref(), emails, conn)
189+
.await?;
190+
dbg!(&user_id);
191+
192+
dbg!(oauth_github_update.insert(conn, user_id).await)?;
193+
194+
Ok(user_id)
195+
}
196+
Err(error) if is_read_only_error(&error) => {
197+
// If we're in read only mode, we can't update their details
198+
// just look for an existing user
199+
find_user_by_gh_id(conn, gh_user.id).await?.ok_or(error)
200+
}
201+
// other error
202+
Err(e) => Err(dbg!(e)),
193203
}
194-
// other error
195-
Err(e) => Err(e),
196-
}
204+
})
205+
.await
197206
}
198207

199208
/// Inserts the user into the database, or updates an existing one.
@@ -206,41 +215,38 @@ async fn create_or_update_user(
206215
emails: &Emails,
207216
conn: &mut AsyncPgConnection,
208217
) -> QueryResult<i32> {
209-
conn.transaction(async |conn| {
210-
let user_id = new_user.insert(conn).await?;
211-
212-
// To send the user an account verification email
213-
if let Some(user_email) = email {
214-
let new_email = NewEmail::builder()
215-
.user_id(user_id)
216-
.email(user_email)
217-
.build();
218-
219-
if let Some(token) = new_email.insert_if_missing(conn).await? {
220-
let email = EmailMessage::from_template(
221-
"user_confirm",
222-
context! {
223-
user_name => new_user.gh_login,
224-
domain => emails.domain,
225-
token => token.expose_secret()
226-
},
227-
);
228-
229-
match email {
230-
Ok(email) => {
231-
// Swallows any error. Some users might insert an invalid email address here.
232-
let _ = emails.send(user_email, email).await;
233-
}
234-
Err(error) => {
235-
warn!("Failed to render user confirmation email template: {error}");
236-
}
237-
};
238-
}
218+
let user_id = new_user.insert(conn).await?;
219+
220+
// To send the user an account verification email
221+
if let Some(user_email) = email {
222+
let new_email = NewEmail::builder()
223+
.user_id(user_id)
224+
.email(user_email)
225+
.build();
226+
227+
if let Some(token) = new_email.insert_if_missing(conn).await? {
228+
let email = EmailMessage::from_template(
229+
"user_confirm",
230+
context! {
231+
user_name => new_user.gh_login,
232+
domain => emails.domain,
233+
token => token.expose_secret()
234+
},
235+
);
236+
237+
match email {
238+
Ok(email) => {
239+
// Swallows any error. Some users might insert an invalid email address here.
240+
let _ = emails.send(user_email, email).await;
241+
}
242+
Err(error) => {
243+
warn!("Failed to render user confirmation email template: {error}");
244+
}
245+
};
239246
}
247+
}
240248

241-
Ok(user_id)
242-
})
243-
.await
249+
Ok(user_id)
244250
}
245251

246252
async fn find_user_by_gh_id(mut conn: &AsyncPgConnection, gh_id: i32) -> QueryResult<Option<i32>> {

src/tests/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::util::{RequestHelper, TestApp};
2-
use crates_io::models::{NewCategory, NewTeam, NewUser};
2+
use crates_io::models::{NewCategory, NewTeam, NewUser, OauthGithubUpdate};
33
use crates_io::views::{
44
EncodableCategory, EncodableCrate, EncodableKeyword, EncodableOwner, EncodableVersion,
55
GoodCrate,
66
};
77

88
use crate::util::github::next_gh_id;
9+
use crates_io::util::gh_token_encryption::GitHubTokenEncryption;
910
use serde::{Deserialize, Serialize};
11+
use std::sync::LazyLock;
1012

1113
mod account_lock;
1214
mod authentication;
@@ -94,6 +96,20 @@ fn new_user(login: &str) -> NewUser<'_> {
9496
NewUser::builder().gh_login(login).build()
9597
}
9698

99+
fn new_oauth_github(login: &str) -> OauthGithubUpdate<'_> {
100+
static ENCRYPTED_TOKEN: LazyLock<Vec<u8>> = LazyLock::new(|| {
101+
GitHubTokenEncryption::for_testing()
102+
.encrypt("some random token")
103+
.unwrap()
104+
});
105+
106+
OauthGithubUpdate::builder()
107+
.account_id(next_gh_id() as i64)
108+
.encrypted_token(&ENCRYPTED_TOKEN)
109+
.login(login)
110+
.build()
111+
}
112+
97113
fn new_team(login: &str) -> NewTeam<'_> {
98114
NewTeam::builder()
99115
.login(login)

src/tests/user.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,19 @@ async fn updating_existing_user_doesnt_change_api_token() -> anyhow::Result<()>
4747
// Use the original API token to find the now updated user
4848
let hashed_token = assert_ok!(HashedToken::parse(token));
4949
let api_token = assert_ok!(ApiToken::find_by_api_token(&mut conn, &hashed_token).await);
50-
let user = assert_ok!(User::find(&conn, api_token.user_id).await);
5150

51+
let user = assert_ok!(User::find(&conn, api_token.user_id).await);
52+
// For now, the user's `gh_login` should be kept in sync; when we stop updating usernames
53+
// from GitHub this test should be changed to assert that the user record remains `foo`
5254
assert_eq!(user.gh_login, "bar");
55+
// The associated `oauth_github` record's username should be updated to `bar`
56+
let oauth_github_login: String = oauth_github::table
57+
.filter(oauth_github::user_id.eq(api_token.user_id))
58+
.select(oauth_github::login)
59+
.first(&mut conn)
60+
.await?;
61+
assert_eq!(oauth_github_login, "bar");
62+
5363
let decrypted_token = encryption.decrypt(&user.gh_encrypted_token.unwrap())?;
5464
assert_eq!(decrypted_token.secret(), "bar_token");
5565

src/tests/util/test_app.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,30 +132,35 @@ impl TestApp {
132132
self.0.test_database.async_connect().await
133133
}
134134

135-
/// Create a new user with a verified email address in the database
136-
/// (`<username>@example.com`) and return a mock user session.
135+
/// Create a new user with:
136+
///
137+
/// - a verified email address in the database (`<username>@example.com`)
138+
/// - an associated `oauth_github` record
139+
///
140+
/// and return a mock user session.
137141
///
138142
/// This method updates the database directly
139143
pub async fn db_new_user(&self, username: &str) -> MockCookieUser {
140144
let conn = self.db_conn().await;
141145

142-
let email = format!("{username}@example.com");
143-
144146
let new_user = crate::new_user(username);
145147
let id = new_user.insert(&conn).await.unwrap();
146148

149+
let email = format!("{username}@example.com");
147150
let new_email = NewEmail::builder()
148151
.user_id(id)
149152
.email(&email)
150153
.verified(true)
151154
.build();
152-
153155
new_email.insert(&conn).await.unwrap();
154156

157+
let oauth_github = crate::new_oauth_github(username);
158+
oauth_github.insert(&conn, id).await.unwrap();
159+
155160
let user = User {
156161
id,
157162
name: new_user.name.map(str::to_string),
158-
gh_id: None,
163+
gh_id: Some(oauth_github.account_id),
159164
gh_login: new_user.gh_login.to_string(),
160165
gh_avatar: None,
161166
gh_encrypted_token: None,

src/tests/worker/sync_admins.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ async fn create_user(
8989
conn: &mut AsyncPgConnection,
9090
) -> QueryResult<()> {
9191
let user_id = diesel::insert_into(users::table)
92-
.values((users::name.eq(name), users::is_admin.eq(is_admin)))
92+
.values((
93+
users::name.eq(name),
94+
users::gh_login.eq(name),
95+
users::is_admin.eq(is_admin),
96+
))
9397
.returning(users::id)
9498
.get_result::<i32>(conn)
9599
.await?;

0 commit comments

Comments
 (0)