Skip to content

Commit

Permalink
WIP sanity check
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoe Spellman committed Feb 20, 2024
1 parent ac506b2 commit 0243582
Show file tree
Hide file tree
Showing 10 changed files with 798 additions and 14 deletions.
532 changes: 532 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ rand = "0.8"
reqwest = "0.11"
serde = { version = "1.0", features = ["derive"] }
sha2 = "0.10"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "time"] }
sqlx = { version = "0.7", features = [
"runtime-tokio-rustls",
"postgres",
"time",
] }
thiserror = "1.0"
time = { version = "0.3", features = ["macros"] }
tokio = { version = "1.36", features = ["full"] }
Expand All @@ -34,3 +38,10 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[build-dependencies]
tonic-build = { version = "0.11", features = ["prost"] }

[dev-dependencies]
cucumber = { version = "0.20.2", features = ["libtest"] }

[[test]]
name = "voting"
harness = false
2 changes: 1 addition & 1 deletion src/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ pub mod rating;
pub mod user;

mod common;
mod pb;
pub mod pb;
22 changes: 22 additions & 0 deletions tests/features/voting.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Feature: User voting
Background:
Given a Snap named "chu-chu-garden" has already accumulated 5 votes and 3 upvotes

Scenario: Amy upvotes a snap she hasn't voted for in the past
When Amy casts an upvote
Then the total number of votes increases
And the total number of upvotes increases

Rule: Votes that a user updates do not change the total vote count

Scenario Outline: Sonic changes his vote between downvote and upvote because "chu-chu-garden" got better/worse
Given Sonic originally voted <original>
When Sonic changes his vote to <after>
Then the total number of of upvotes <direction>
But the total number of votes stays constant

Examples:
| original | after | direction |
| upvote | downvote | decreases |
| downvote | upvote | increases |

8 changes: 6 additions & 2 deletions tests/helpers/client_app.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use futures::task::noop_waker;

Check warning on line 1 in tests/helpers/client_app.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

unused import: `futures::task::noop_waker`
use tonic::{metadata::MetadataValue, transport::Endpoint, Request, Response, Status};

use crate::pb::app::{GetRatingRequest, GetRatingResponse};
use ratings::features::pb::app::{GetRatingRequest, GetRatingResponse};

use crate::pb::app::app_client as pb;
use ratings::features::pb::app::app_client as pb;

pub trait AppClient {}

#[derive(Debug, Clone)]

Check failure on line 10 in tests/helpers/client_app.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

expected struct, variant or union type, found trait `AppClient`

Check failure on line 10 in tests/helpers/client_app.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

trait objects must include the `dyn` keyword

Check failure on line 10 in tests/helpers/client_app.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

trait objects must include the `dyn` keyword
pub struct AppClient {

Check failure on line 11 in tests/helpers/client_app.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

the name `AppClient` is defined multiple times
Expand All @@ -25,6 +28,7 @@ impl AppClient {
.unwrap()
.connect()
.await
.noop_waker()
.unwrap();
let mut client = pb::AppClient::with_interceptor(channel, move |mut req: Request<()>| {
let header: MetadataValue<_> = format!("Bearer {token}").parse().unwrap();
Expand Down
4 changes: 2 additions & 2 deletions tests/helpers/client_chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use tonic::metadata::MetadataValue;
use tonic::transport::Endpoint;
use tonic::{Request, Response, Status};

use crate::pb::chart::{chart_client as pb, Timeframe};
use crate::pb::chart::{GetChartRequest, GetChartResponse};
use ratings::features::pb::chart::{chart_client as pb, Timeframe};
use ratings::features::pb::chart::{GetChartRequest, GetChartResponse};

#[derive(Debug, Clone)]
pub struct ChartClient {
Expand Down
4 changes: 2 additions & 2 deletions tests/helpers/client_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use tonic::metadata::MetadataValue;
use tonic::transport::Endpoint;
use tonic::{Request, Response, Status};

use crate::pb::user::user_client as pb;
use crate::pb::user::{
use ratings::features::pb::user::user_client as pb;
use ratings::features::pb::user::{
AuthenticateRequest, AuthenticateResponse, GetSnapVotesRequest, GetSnapVotesResponse,
VoteRequest,
};
Expand Down
Empty file added tests/helpers/test_client.rs
Empty file.
12 changes: 6 additions & 6 deletions tests/helpers/vote_generator.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use super::test_data::TestData;
use super::{client_user::UserClient, test_data::TestData};

Check warning on line 1 in tests/helpers/vote_generator.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

unused import: `test_data::TestData`
use crate::helpers;
use crate::pb::user::{AuthenticateResponse, VoteRequest};
use cucumber::cli;

Check warning on line 3 in tests/helpers/vote_generator.rs

View workflow job for this annotation

GitHub Actions / build / Build Ratings

unused import: `cucumber::cli`
use ratings::features::pb::user::{AuthenticateResponse, VoteRequest};

pub async fn generate_votes(
snap_id: &str,
snap_revision: i32,
vote_up: bool,
count: u64,
data: TestData,
client: &UserClient,
) -> Result<(), Box<dyn std::error::Error>> {
for _ in 0..count {
register_and_vote(snap_id, snap_revision, vote_up, data.clone()).await?;
register_and_vote(snap_id, snap_revision, vote_up, client).await?;
}
Ok(())
}
Expand All @@ -19,9 +20,8 @@ async fn register_and_vote(
snap_id: &str,
snap_revision: i32,
vote_up: bool,
data: TestData,
client: &UserClient,
) -> Result<(), Box<dyn std::error::Error>> {
let client = data.user_client.clone().unwrap();
let id: String = helpers::data_faker::rnd_sha_256();
let response: AuthenticateResponse = client
.authenticate(&id)
Expand Down
215 changes: 215 additions & 0 deletions tests/voting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
use std::str::FromStr;

use cucumber::{given, then, when, Parameter, World};
use helpers::client_user::UserClient;
use ratings::{
features::pb::user::{GetSnapVotesRequest, VoteRequest},
utils::Config,
};

mod helpers;

#[derive(Debug, Default)]
struct AuthenticatedUser {
token: String,
}

#[derive(Debug, Default, Copy, Clone, Parameter)]
#[param(name = "vote-type", regex = "upvote|downvote")]
enum VoteType {
#[default]
Upvote,
Downvote,
}

impl FromStr for VoteType {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"upvote" => Self::Upvote,
"downvote" => Self::Downvote,
_ => return Err(format!("invalid vote type {s}")),
})
}
}

impl From<VoteType> for bool {
fn from(value: VoteType) -> Self {
match value {
VoteType::Upvote => true,
VoteType::Downvote => false,
}
}
}

impl From<VoteType> for u64 {
fn from(value: VoteType) -> Self {
bool::from(value) as u64
}
}

#[derive(Debug, Default, Copy, Clone, Parameter)]
#[param(name = "direction", regex = "increases|decreases|stays constant")]
enum Direction {
#[default]
Increase,
Decrease,
StaysConstant,
}

impl FromStr for Direction {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"increases" => Self::Increase,
"decreases" => Self::Decrease,
"stays constant" => Self::StaysConstant,
_ => return Err(format!("invalid vote count direction {s}")),
})
}
}

impl Direction {
fn check_and_apply(&self, current: &mut u64, new: u64) {
match self {
Direction::Decrease => {
assert_eq!(new, *current - 1);
*current -= 1;
}
Direction::Increase => {
assert_eq!(new, *current + 1);
*current += 1;
}
Direction::StaysConstant => assert_eq!(new, *current),
};
}
}

#[derive(Debug, PartialEq, Eq)]
struct Snap(String);

impl Default for Snap {
fn default() -> Self {
Snap("93jv9vhsfbb8f7".to_string())
}
}

#[derive(Debug, World)]
#[world(init = Self::new)]
struct VotingWorld {
user: AuthenticatedUser,
client: UserClient,
snap: Snap,
votes: u64,
upvotes: u64,
}

impl VotingWorld {
async fn new() -> Self {
let config = Config::load().expect("Could not load config");
let client = UserClient::new(&config.socket());

let id = helpers::data_faker::rnd_sha_256();
let user = AuthenticatedUser {
token: client
.authenticate(&id)
.await
.expect("could not authenticate user")
.into_inner()
.token,
};

VotingWorld {
user,
client,
snap: Default::default(),
votes: 0,
upvotes: 0,
}
}
}

#[given(expr = "a Snap named {string} has already accumulated {int} votes and {int} upvotes")]
async fn seed_snap(world: &mut VotingWorld, _snap_name: String, votes: u64, upvotes: u64) {
world.snap.0 = helpers::data_faker::rnd_id();
helpers::vote_generator::generate_votes(&world.snap.0, 1, true, upvotes, &world.client)
.await
.expect("could not generate votes");
helpers::vote_generator::generate_votes(
&world.snap.0,
1,
false,
votes - upvotes,
&world.client,
)
.await
.expect("could not generate votes");

world.votes = votes;
world.upvotes = upvotes;
}

#[when(expr = "{word} casts a(n) {vote-type}")]
#[given(expr = "{word} originally voted {vote-type}")]
#[then(expr = "{word} changes his/her/their vote to {vote-type}")]
async fn vote(world: &mut VotingWorld, _user_name: String, vote_type: VoteType) {
let request = VoteRequest {
snap_id: world.snap.0.clone(),
snap_revision: 1,
vote_up: vote_type.into(),
};

world
.client
.vote(&world.user.token, request)
.await
.expect("could not cast vote");
}

#[then(expr = "the total number of votes {direction}")]
async fn check_vote(world: &mut VotingWorld, direction: Direction) {
let votes = world
.client
.get_snap_votes(
&world.user.token,
GetSnapVotesRequest {
snap_id: world.snap.0.clone(),
},
)
.await
.expect("could not get snap votes")
.into_inner();
panic!("{:?}", votes);

let votes = votes.votes.len();

direction.check_and_apply(&mut world.votes, votes as u64);
}

#[then(expr = "The total number of upvotes {direction}")]
async fn check_upvote(world: &mut VotingWorld, direction: Direction) {
let upvotes: u64 = world
.client
.get_snap_votes(
&world.user.token,
GetSnapVotesRequest {
snap_id: world.snap.0.clone(),
},
)
.await
.expect("could not get snap votes")
.into_inner()
.votes
.into_iter()
.map(|v| v.vote_up as u64)
.sum();

direction.check_and_apply(&mut world.upvotes, upvotes as u64);
}

#[tokio::main]
async fn main() {
VotingWorld::run("tests/features/voting.feature").await;
}

0 comments on commit 0243582

Please sign in to comment.