Skip to content

Commit 82cc5cd

Browse files
committed
db: add database tests
1 parent 2df41e1 commit 82cc5cd

File tree

13 files changed

+397
-75
lines changed

13 files changed

+397
-75
lines changed

.github/workflows/ci.yml

+8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ jobs:
1616
- name: Install stable Rust
1717
run: rustup update stable && rustup default stable
1818

19+
- name: Install PostgreSQL
20+
run: |
21+
sudo apt-get update && DEBIAN_FRONTEND=noninteractive sudo apt-get install -y postgresql
22+
sudo systemctl start postgresql
23+
sudo -u postgres createuser $(whoami) -w
24+
sudo -u postgres createdb $(whoami) -O $(whoami)
25+
echo "::set-env name=CRATESFYI_DATABASE_URL::postgresql://$(whoami)@%2Fvar%2Frun%2Fpostgresql/$(whoami)"
26+
1927
- name: Build docs.rs
2028
run: cargo build
2129

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ If you need to store big files in the repository's directory it's recommended to
8080
put them in the `ignored/` subdirectory, which is ignored both by git and
8181
Docker.
8282

83+
### Running tests
84+
85+
Tests are run outside of the docker-compose environment, and can be run with:
86+
87+
```
88+
cargo test
89+
```
90+
91+
Some tests require access to the database. To run them, set the
92+
`CRATESFYI_DATABASE_URL` to the url of a PostgreSQL database. You don't have to
93+
run the migrations on it or ensure it's empty, as all the tests use temporary
94+
tables to prevent conflicts with each other or existing data.
95+
8396
### Docker-Compose
8497

8598
#### Rebuilding Containers

src/bin/cratesfyi.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ pub fn main() {
202202
if let Some(matches) = matches.subcommand_matches("migrate") {
203203
let version = matches.value_of("VERSION").map(|v| v.parse::<i64>()
204204
.expect("Version should be an integer"));
205-
db::migrate(version).expect("Failed to run database migrations");
205+
db::migrate(version, &connect_db().expect("failed to connect to the database"))
206+
.expect("Failed to run database migrations");
206207
} else if let Some(_) = matches.subcommand_matches("update-github-fields") {
207208
cratesfyi::utils::github_updater().expect("Failed to update github fields");
208209
} else if let Some(matches) = matches.subcommand_matches("add-directory") {

src/db/add_package.rs

+72-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11

2-
use Metadata;
32
use utils::MetadataPackage;
43
use docbuilder::BuildResult;
54
use regex::Regex;
@@ -9,6 +8,7 @@ use std::io::BufReader;
98
use std::path::Path;
109
use std::fs;
1110

11+
use time::Timespec;
1212
use rustc_serialize::json::{Json, ToJson};
1313
use slug::slugify;
1414
use reqwest::Client;
@@ -28,6 +28,8 @@ pub(crate) fn add_package_into_database(conn: &Connection,
2828
res: &BuildResult,
2929
files: Option<Json>,
3030
doc_targets: Vec<String>,
31+
default_target: &Option<String>,
32+
cratesio_data: &CratesIoData,
3133
has_docs: bool,
3234
has_examples: bool)
3335
-> Result<i32> {
@@ -36,9 +38,7 @@ pub(crate) fn add_package_into_database(conn: &Connection,
3638
let dependencies = convert_dependencies(metadata_pkg);
3739
let rustdoc = get_rustdoc(metadata_pkg, source_dir).unwrap_or(None);
3840
let readme = get_readme(metadata_pkg, source_dir).unwrap_or(None);
39-
let (release_time, yanked, downloads) = get_release_time_yanked_downloads(metadata_pkg)?;
4041
let is_library = metadata_pkg.is_library();
41-
let metadata = Metadata::from_source_dir(source_dir)?;
4242

4343
let release_id: i32 = {
4444
let rows = conn.query("SELECT id FROM releases WHERE crate_id = $1 AND version = $2",
@@ -61,10 +61,10 @@ pub(crate) fn add_package_into_database(conn: &Connection,
6161
RETURNING id",
6262
&[&crate_id,
6363
&metadata_pkg.version,
64-
&release_time,
64+
&cratesio_data.release_time,
6565
&dependencies.to_json(),
6666
&metadata_pkg.package_name(),
67-
&yanked,
67+
&cratesio_data.yanked,
6868
&res.successful,
6969
&has_docs,
7070
&false, // TODO: Add test status somehow
@@ -77,13 +77,13 @@ pub(crate) fn add_package_into_database(conn: &Connection,
7777
&metadata_pkg.authors.to_json(),
7878
&metadata_pkg.keywords.to_json(),
7979
&has_examples,
80-
&downloads,
80+
&cratesio_data.downloads,
8181
&files,
8282
&doc_targets.to_json(),
8383
&is_library,
8484
&res.rustc_version,
8585
&metadata_pkg.documentation,
86-
&metadata.default_target])?;
86+
&default_target])?;
8787
// return id
8888
rows.get(0).get(0)
8989

@@ -115,10 +115,10 @@ pub(crate) fn add_package_into_database(conn: &Connection,
115115
WHERE crate_id = $1 AND version = $2",
116116
&[&crate_id,
117117
&format!("{}", metadata_pkg.version),
118-
&release_time,
118+
&cratesio_data.release_time,
119119
&dependencies.to_json(),
120120
&metadata_pkg.package_name(),
121-
&yanked,
121+
&cratesio_data.yanked,
122122
&res.successful,
123123
&has_docs,
124124
&false, // TODO: Add test status somehow
@@ -131,21 +131,21 @@ pub(crate) fn add_package_into_database(conn: &Connection,
131131
&metadata_pkg.authors.to_json(),
132132
&metadata_pkg.keywords.to_json(),
133133
&has_examples,
134-
&downloads,
134+
&cratesio_data.downloads,
135135
&files,
136136
&doc_targets.to_json(),
137137
&is_library,
138138
&res.rustc_version,
139139
&metadata_pkg.documentation,
140-
&metadata.default_target])?;
140+
&default_target])?;
141141
rows.get(0).get(0)
142142
}
143143
};
144144

145145

146146
add_keywords_into_database(&conn, &metadata_pkg, &release_id)?;
147147
add_authors_into_database(&conn, &metadata_pkg, &release_id)?;
148-
add_owners_into_database(&conn, &metadata_pkg, &crate_id)?;
148+
add_owners_into_database(&conn, &cratesio_data.owners, &crate_id)?;
149149

150150

151151
// Update versions
@@ -284,6 +284,27 @@ fn read_rust_doc(file_path: &Path) -> Result<Option<String>> {
284284
}
285285

286286

287+
pub(crate) struct CratesIoData {
288+
pub(crate) release_time: Timespec,
289+
pub(crate) yanked: bool,
290+
pub(crate) downloads: i32,
291+
pub(crate) owners: Vec<CrateOwner>,
292+
}
293+
294+
impl CratesIoData {
295+
pub(crate) fn get_from_network(pkg: &MetadataPackage) -> Result<Self> {
296+
let (release_time, yanked, downloads) = get_release_time_yanked_downloads(pkg)?;
297+
let owners = get_owners(pkg)?;
298+
299+
Ok(Self {
300+
release_time,
301+
yanked,
302+
downloads,
303+
owners,
304+
})
305+
}
306+
}
307+
287308

288309
/// Get release_time, yanked and downloads from crates.io
289310
fn get_release_time_yanked_downloads(
@@ -394,9 +415,15 @@ fn add_authors_into_database(conn: &Connection, pkg: &MetadataPackage, release_i
394415
}
395416

396417

418+
pub(crate) struct CrateOwner {
419+
pub(crate) avatar: String,
420+
pub(crate) email: String,
421+
pub(crate) login: String,
422+
pub(crate) name: String,
423+
}
397424

398-
/// Adds owners into database
399-
fn add_owners_into_database(conn: &Connection, pkg: &MetadataPackage, crate_id: &i32) -> Result<()> {
425+
/// Fetch owners from crates.io
426+
fn get_owners(pkg: &MetadataPackage) -> Result<Vec<CrateOwner>> {
400427
// owners available in: https://crates.io/api/v1/crates/rand/owners
401428
let owners_url = format!("https://crates.io/api/v1/crates/{}/owners", pkg.name);
402429
let client = Client::new();
@@ -409,6 +436,7 @@ fn add_owners_into_database(conn: &Connection, pkg: &MetadataPackage, crate_id:
409436
res.read_to_string(&mut body).unwrap();
410437
let json = Json::from_str(&body[..])?;
411438

439+
let mut result = Vec::new();
412440
if let Some(owners) = json.as_object()
413441
.and_then(|j| j.get("users"))
414442
.and_then(|j| j.as_array()) {
@@ -435,25 +463,38 @@ fn add_owners_into_database(conn: &Connection, pkg: &MetadataPackage, crate_id:
435463
continue;
436464
}
437465

438-
let owner_id: i32 = {
439-
let rows = conn.query("SELECT id FROM owners WHERE login = $1", &[&login])?;
440-
if rows.len() > 0 {
441-
rows.get(0).get(0)
442-
} else {
443-
conn.query("INSERT INTO owners (login, avatar, name, email)
444-
VALUES ($1, $2, $3, $4)
445-
RETURNING id",
446-
&[&login, &avatar, &name, &email])?
447-
.get(0)
448-
.get(0)
449-
}
450-
};
451-
452-
// add relationship
453-
let _ = conn.query("INSERT INTO owner_rels (cid, oid) VALUES ($1, $2)",
454-
&[crate_id, &owner_id]);
466+
result.push(CrateOwner {
467+
avatar: avatar.to_string(),
468+
email: email.to_string(),
469+
login: login.to_string(),
470+
name: name.to_string(),
471+
});
455472
}
473+
}
474+
475+
Ok(result)
476+
}
477+
478+
/// Adds owners into database
479+
fn add_owners_into_database(conn: &Connection, owners: &[CrateOwner], crate_id: &i32) -> Result<()> {
480+
for owner in owners {
481+
let owner_id: i32 = {
482+
let rows = conn.query("SELECT id FROM owners WHERE login = $1", &[&owner.login])?;
483+
if rows.len() > 0 {
484+
rows.get(0).get(0)
485+
} else {
486+
conn.query("INSERT INTO owners (login, avatar, name, email)
487+
VALUES ($1, $2, $3, $4)
488+
RETURNING id",
489+
&[&owner.login, &owner.avatar, &owner.name, &owner.email])?
490+
.get(0)
491+
.get(0)
492+
}
493+
};
456494

495+
// add relationship
496+
let _ = conn.query("INSERT INTO owner_rels (cid, oid) VALUES ($1, $2)",
497+
&[crate_id, &owner_id]);
457498
}
458499
Ok(())
459500
}

src/db/delete_crate.rs

+57
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,60 @@ fn delete_prefix_from_s3(s3: &S3Client, name: &str) -> Result<(), Error> {
120120
}
121121
}
122122
}
123+
124+
#[cfg(test)]
125+
mod tests {
126+
use super::*;
127+
use failure::Error;
128+
use postgres::Connection;
129+
130+
#[test]
131+
fn test_delete_from_database() {
132+
fn crate_exists(conn: &Connection, name: &str) -> Result<bool, Error> {
133+
Ok(!conn
134+
.query("SELECT * FROM crates WHERE name = $1;", &[&name])?
135+
.is_empty())
136+
}
137+
fn release_exists(conn: &Connection, id: i32) -> Result<bool, Error> {
138+
Ok(!conn
139+
.query("SELECT * FROM releases WHERE id = $1;", &[&id])?
140+
.is_empty())
141+
}
142+
143+
crate::test::with_database(|db| {
144+
// Create fake packages in the database
145+
let pkg1_v1_id = db
146+
.fake_release()
147+
.name("package-1")
148+
.version("1.0.0")
149+
.create()?;
150+
let pkg1_v2_id = db
151+
.fake_release()
152+
.name("package-1")
153+
.version("2.0.0")
154+
.create()?;
155+
let pkg2_id = db.fake_release().name("package-2").create()?;
156+
157+
assert!(crate_exists(db.conn(), "package-1")?);
158+
assert!(crate_exists(db.conn(), "package-2")?);
159+
assert!(release_exists(db.conn(), pkg1_v1_id)?);
160+
assert!(release_exists(db.conn(), pkg1_v2_id)?);
161+
assert!(release_exists(db.conn(), pkg2_id)?);
162+
163+
let pkg1_id = db.conn()
164+
.query("SELECT id FROM crates WHERE name = 'package-1';", &[])?
165+
.get(0)
166+
.get("id");
167+
168+
delete_from_database(db.conn(), "package-1", pkg1_id)?;
169+
170+
assert!(!crate_exists(db.conn(), "package-1")?);
171+
assert!(crate_exists(db.conn(), "package-2")?);
172+
assert!(!release_exists(db.conn(), pkg1_v1_id)?);
173+
assert!(!release_exists(db.conn(), pkg1_v2_id)?);
174+
assert!(release_exists(db.conn(), pkg2_id)?);
175+
176+
Ok(())
177+
});
178+
}
179+
}

0 commit comments

Comments
 (0)