use crate::{
add_all, AccessToken, GetFileRequest, GetFileResponse, GetFilesResponse, LoginResponse,
PatchFileRequest, Service, SignupRequest,
};
use async_trait::async_trait;
use endbasic_std::storage::Storage;
use endbasic_std::testutils::*;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io;
use std::rc::Rc;
#[derive(Default)]
pub struct MockService {
access_token: Option<AccessToken>,
mock_signup: VecDeque<(SignupRequest, io::Result<()>)>,
mock_login: VecDeque<((String, String), io::Result<LoginResponse>)>,
mock_get_files: VecDeque<(String, io::Result<GetFilesResponse>)>,
mock_get_file: VecDeque<((String, String, GetFileRequest), io::Result<GetFileResponse>)>,
mock_patch_file: VecDeque<((String, String, PatchFileRequest), io::Result<()>)>,
mock_delete_file: VecDeque<((String, String), io::Result<()>)>,
}
impl MockService {
#[cfg(test)]
pub(crate) async fn do_login(&mut self) {
self.access_token = match &self.access_token {
Some(previous) => Some(AccessToken::new(format!("{}$", previous.as_str()))),
None => Some(AccessToken::new("$")),
}
}
#[cfg(test)]
pub(crate) fn add_mock_signup(&mut self, request: SignupRequest, result: io::Result<()>) {
self.mock_signup.push_back((request, result));
}
#[cfg(test)]
pub(crate) fn add_mock_login(
&mut self,
username: &str,
password: &str,
result: io::Result<LoginResponse>,
) {
let exp_request = (username.to_owned(), password.to_owned());
self.mock_login.push_back((exp_request, result));
}
#[cfg(test)]
pub(crate) fn add_mock_get_files(
&mut self,
username: &str,
result: io::Result<GetFilesResponse>,
) {
let exp_request = username.to_owned();
self.mock_get_files.push_back((exp_request, result));
}
#[cfg(test)]
pub(crate) fn add_mock_get_file(
&mut self,
username: &str,
filename: &str,
exp_request: GetFileRequest,
result: io::Result<GetFileResponse>,
) {
let exp_request = (username.to_owned(), filename.to_owned(), exp_request);
self.mock_get_file.push_back((exp_request, result));
}
#[cfg(test)]
pub(crate) fn add_mock_patch_file(
&mut self,
username: &str,
filename: &str,
exp_request: PatchFileRequest,
result: io::Result<()>,
) {
let exp_request = (username.to_owned(), filename.to_owned(), exp_request);
self.mock_patch_file.push_back((exp_request, result));
}
#[cfg(test)]
pub(crate) fn add_mock_delete_file(
&mut self,
username: &str,
filename: &str,
result: io::Result<()>,
) {
let exp_request = (username.to_owned(), filename.to_owned());
self.mock_delete_file.push_back((exp_request, result));
}
pub(crate) fn verify_all_used(&mut self) {
assert!(self.mock_signup.is_empty(), "Mock requests not fully consumed");
assert!(self.mock_login.is_empty(), "Mock requests not fully consumed");
assert!(self.mock_get_files.is_empty(), "Mock requests not fully consumed");
assert!(self.mock_get_file.is_empty(), "Mock requests not fully consumed");
assert!(self.mock_patch_file.is_empty(), "Mock requests not fully consumed");
assert!(self.mock_delete_file.is_empty(), "Mock requests not fully consumed");
}
}
#[async_trait(?Send)]
impl Service for MockService {
async fn signup(&mut self, request: &SignupRequest) -> io::Result<()> {
let mock = self.mock_signup.pop_front().expect("No mock requests available");
assert_eq!(&mock.0, request);
mock.1
}
async fn login(&mut self, username: &str, password: &str) -> io::Result<LoginResponse> {
let mock = self.mock_login.pop_front().expect("No mock requests available");
assert_eq!(&mock.0 .0, username);
assert_eq!(&mock.0 .1, password);
if let Ok(response) = &mock.1 {
self.access_token = Some(response.access_token.clone());
}
mock.1
}
async fn logout(&mut self) -> io::Result<()> {
self.access_token.as_ref().expect("login not called yet");
self.access_token = None;
Ok(())
}
fn is_logged_in(&self) -> bool {
self.access_token.is_some()
}
fn logged_in_username(&self) -> Option<String> {
match self.access_token {
Some(_) => Some("logged-in-username".to_owned()),
None => None,
}
}
async fn get_files(&mut self, username: &str) -> io::Result<GetFilesResponse> {
self.access_token.as_ref().expect("login not called yet");
let mock = self.mock_get_files.pop_front().expect("No mock requests available");
assert_eq!(&mock.0, username);
mock.1
}
async fn get_file(
&mut self,
username: &str,
filename: &str,
request: &GetFileRequest,
) -> io::Result<GetFileResponse> {
self.access_token.as_ref().expect("login not called yet");
let mock = self.mock_get_file.pop_front().expect("No mock requests available");
assert_eq!(&mock.0 .0, username);
assert_eq!(&mock.0 .1, filename);
assert_eq!(&mock.0 .2, request);
mock.1
}
async fn patch_file(
&mut self,
username: &str,
filename: &str,
request: &PatchFileRequest,
) -> io::Result<()> {
self.access_token.as_ref().expect("login not called yet");
let mock = self.mock_patch_file.pop_front().expect("No mock requests available");
assert_eq!(&mock.0 .0, username);
assert_eq!(&mock.0 .1, filename);
assert_eq!(&mock.0 .2, request);
mock.1
}
async fn delete_file(&mut self, username: &str, filename: &str) -> io::Result<()> {
self.access_token.as_ref().expect("login not called yet");
let mock = self.mock_delete_file.pop_front().expect("No mock requests available");
assert_eq!(&mock.0 .0, username);
assert_eq!(&mock.0 .1, filename);
mock.1
}
}
#[must_use]
pub(crate) struct ClientTester {
tester: Tester,
service: Rc<RefCell<MockService>>,
}
impl Default for ClientTester {
fn default() -> Self {
let mut tester = Tester::default();
let console = tester.get_console();
let storage = tester.get_storage();
let service = Rc::from(RefCell::from(MockService::default()));
add_all(
tester.get_machine(),
service.clone(),
console,
storage,
"https://repl.example.com/",
);
ClientTester { tester, service }
}
}
impl ClientTester {
pub fn add_input_chars(self, golden_in: &str) -> Self {
ClientTester { tester: self.tester.add_input_chars(golden_in), service: self.service }
}
pub fn get_console(&self) -> Rc<RefCell<MockConsole>> {
self.tester.get_console()
}
pub(crate) fn get_service(&self) -> Rc<RefCell<MockService>> {
self.service.clone()
}
pub fn get_storage(&self) -> Rc<RefCell<Storage>> {
self.tester.get_storage()
}
pub(crate) fn run<S: Into<String>>(&mut self, script: S) -> ClientChecker {
let checker = self.tester.run(script);
ClientChecker { checker, service: self.service.clone(), exp_access_token: None }
}
}
#[must_use]
pub(crate) struct ClientChecker<'a> {
checker: Checker<'a>,
service: Rc<RefCell<MockService>>,
exp_access_token: Option<AccessToken>,
}
impl<'a> ClientChecker<'a> {
pub(crate) fn expect_access_token<S: Into<String>>(self, token: S) -> Self {
Self {
checker: self.checker,
service: self.service,
exp_access_token: Some(AccessToken::new(token.into())),
}
}
pub fn expect_err<S: Into<String>>(self, message: S) -> Self {
Self {
checker: self.checker.expect_err(message),
service: self.service,
exp_access_token: self.exp_access_token,
}
}
pub fn expect_file<N: Into<String>, C: Into<String>>(self, name: N, content: C) -> Self {
Self {
checker: self.checker.expect_file(name, content),
service: self.service,
exp_access_token: self.exp_access_token,
}
}
pub fn expect_output<V: Into<Vec<CapturedOut>>>(self, out: V) -> Self {
Self {
checker: self.checker.expect_output(out),
service: self.service,
exp_access_token: self.exp_access_token,
}
}
pub fn expect_prints<S: Into<String>, V: Into<Vec<S>>>(self, out: V) -> Self {
Self {
checker: self.checker.expect_prints(out),
service: self.service,
exp_access_token: self.exp_access_token,
}
}
#[must_use]
pub fn take_captured_out(&mut self) -> Vec<CapturedOut> {
self.checker.take_captured_out()
}
pub(crate) fn check(self) {
self.checker.check();
let mut service = self.service.borrow_mut();
assert_eq!(self.exp_access_token, service.access_token);
service.verify_all_used();
}
}
pub fn client_check_stmt_err<S: Into<String>>(exp_error: S, stmt: &str) {
ClientTester::default().run(stmt).expect_err(exp_error).check();
}