Skip to content

Commit

Permalink
Merge pull request #209 from jeremyandrews/umami-admin
Browse files Browse the repository at this point in the history
add logged in admin user to umami load test
  • Loading branch information
jeremyandrews authored Nov 1, 2020
2 parents fe808cf + b8fda8c commit 091faa7
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 8 deletions.
29 changes: 23 additions & 6 deletions examples/umami/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ By default it will try and load pages from https://drupal-9.0.7.ddev.site/.
The load test is split into the following files:
- `main.rs`: This file contains the main() function and defines the actual load test;
- `common.rs`: This file contains helper functions used by the task functions;
- `english.rs`: This files contains all task functions loading pages in English;
- `spanish.rs`: This files contains all task functions loading pages in Spanish.
- `english.rs`: This file contains all task functions loading pages in English;
- `spanish.rs`: This file contains all task functions loading pages in Spanish;
- `admin.rs`: This file contains all task functions specific to simulating an admin user.

## Load Test Features

The load test defines the following users:
- Anonymous English user: this user performs all tasks in English, it runs 3 times as often as the Spanish user;
- Anonymous Spanish user: this user performs all tasks in Spanish, it runs 1/3 as often as the English user.
- Anonymous English user: this user performs all tasks in English, it has a weight of 40, and randomly pauses for 0 to 3 seconds after each task;
- Anonymous Spanish user: this user performs all tasks in Spanish, it has a weight of 9, and randomly pauses for 0 to 3 seconds after each task;
- Admin user: this user logs into the website, it has a weight of 1, and randomly pauses for 3 to 10 seconds after each task.

Each load test user runs the following tasks in their own language, and also loads all static elements on any pages loaded:
Due to user weighting, the load test should simulate at least 50 users when it runs. If you simulate 100 users (with the `-u 100` run time option) then 80 anonymous English users, 18 anonymous Spanish users, and 2 admin users will be simulated.

Each anonymous load test user runs the following tasks in their own language, and also loads all static elements on any pages loaded:
- Loads the front page;
- Loads a "basic page";
- Loads the article listing page;
Expand All @@ -32,4 +36,17 @@ Each load test user runs the following tasks in their own language, and also loa
- Loads a random node by nid;
- Loads the term listing page filtered by a random term;
- Performs a search using a random word from a random node's title;
- Submits website feedback through the contact form;
- Submits website feedback through the contact form.

Each admin load test user logs in one time in English, and then runs the following tasks and also loads all static elements on any pages loaded:
- Loads the front page;
- Loads the article listing page;
- Loads an "article", edits (not making any actual changes), and saves it (flushing all caches).

## Configuring The Admin User

The load test needs to know what username and password to use to log in. By default it will attempt to log in with the username `admin` and the password `P@ssw0rd1234`. However, you can use the ADMIN_USERNAME and/or ADMIN_PASSWORD environment variables to log in with different values. In the following example, the load test will attempt to log in with the username `foo` and the password `bar`:

```
ADMIN_USERNAME=foo ADMIN_PASSWORD=bar cargo run --release --example umami -- -H https://drupal-9.0.7.ddev.site -v -u50
```
210 changes: 210 additions & 0 deletions examples/umami/admin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
use goose::prelude::*;

use crate::common;

use rand::seq::SliceRandom;
use std::env;

/// Log into the website.
pub async fn log_in(user: &GooseUser) -> GooseTaskResult {
// Use ADMIN_USERNAME= to set custom admin username.
let admin_username = match env::var("ADMIN_USERNAME") {
Ok(username) => username,
Err(_) => "admin".to_string(),
};
// Use ADMIN_PASSWORD= to set custom admin username.
let admin_password = match env::var("ADMIN_PASSWORD") {
Ok(password) => password,
Err(_) => "P@ssw0rd1234".to_string(),
};

// Load the log in page.
let mut goose = user.get("/en/user/login").await?;

// We can't invoke common::validate_and_load_static_assets as while it's important
// to validate the page and load static elements, we then need to extract form elements
// from the HTML of the page. So we duplicate some of the logic, enhancing it for form
// processing.
let mut logged_in_user;
match goose.response {
Ok(response) => {
// Copy the headers so we have them for logging if there are errors.
let headers = &response.headers().clone();
match response.text().await {
Ok(html) => {
// Be sure we've properly loaded the log in page.
let title = "Log in";
if !common::valid_title(&html, title) {
return user.set_failure(
&format!("{}: title not found: {}", &goose.request.url, title),
&mut goose.request,
Some(&headers),
Some(&html),
);
}

// Load all static elements on the page, as a real user would.
common::load_static_elements(user, &html).await;

// Scrape the HTML to get the values needed in order to POST to the
// log in form.
let form_build_id = common::get_form_value(&html, "form_build_id");
if form_build_id.is_none() {
return user.set_failure(
&format!("{}: no form_build_id on page", goose.request.url),
&mut goose.request,
Some(&headers),
Some(&html),
);
}

// Build log in form with username and password from environment.
let params = [
("name", &admin_username),
("pass", &admin_password),
("form_build_id", &form_build_id.unwrap()),
("form_id", &"user_login_form".to_string()),
("op", &"Log+in".to_string()),
];
let request_builder = user.goose_post("/en/user/login").await?;
logged_in_user = user.goose_send(request_builder.form(&params), None).await?;

// A successful log in is redirected.
if !logged_in_user.request.redirected {
return user.set_failure(
&format!(
"{}: login failed (check ADMIN_USERNAME and ADMIN_PASSWORD)",
logged_in_user.request.final_url
),
&mut logged_in_user.request,
Some(&headers),
None,
);
}
}
Err(e) => {
return user.set_failure(
&format!("{}: failed to parse page: {}", goose.request.url, e),
&mut goose.request,
Some(&headers),
None,
);
}
}
}
Err(e) => {
return user.set_failure(
&format!("{}: no response from server: {}", goose.request.url, e),
&mut goose.request,
None,
None,
);
}
}
// Check the title to verify that the user is logged in.
common::validate_and_load_static_assets(user, logged_in_user, &admin_username).await?;

Ok(())
}

/// Load and edit a random article.
pub async fn edit_article(user: &GooseUser) -> GooseTaskResult {
// First, load a random article.
let nodes = common::get_nodes(&common::ContentType::Article);
let article = nodes.choose(&mut rand::thread_rng());
let goose = user.get(article.unwrap().url_en).await?;
common::validate_and_load_static_assets(user, goose, article.unwrap().title_en).await?;

// Next, load the edit link for the chosen article.
let mut goose = user
.get(&format!("/en/node/{}/edit", article.unwrap().nid))
.await?;

let mut saved_article;
match goose.response {
Ok(response) => {
// Copy the headers so we have them for logging if there are errors.
let headers = &response.headers().clone();
match response.text().await {
Ok(html) => {
// Be sure we've properly loaded the edit page.
let title = "Edit Article";
if !common::valid_title(&html, title) {
return user.set_failure(
&format!("{}: title not found: {}", &goose.request.url, title),
&mut goose.request,
Some(&headers),
Some(&html),
);
}

// Load all static elements on the page, as a real user would.
common::load_static_elements(user, &html).await;

// Scrape the HTML to get the values needed in order to POST to the
// log in form.
let form_build_id = common::get_form_value(&html, "form_build_id");
if form_build_id.is_none() {
return user.set_failure(
&format!("{}: no form_build_id on page", goose.request.url),
&mut goose.request,
Some(&headers),
Some(&html),
);
}
let form_token = common::get_form_value(&html, "form_token");
if form_token.is_none() {
return user.set_failure(
&format!("{}: no form_token on page", goose.request.url),
&mut goose.request,
Some(&headers),
Some(&html),
);
}

// Build node form with random word from title.
let params = [
("form_build_id", &form_build_id.unwrap()),
("form_token", &form_token.unwrap()),
("form_id", &"node_article_edit_form".to_string()),
("op", &"Save (this translation)".to_string()),
];
let request_builder = user
.goose_post(&format!("/en/node/{}/edit", article.unwrap().nid))
.await?;
saved_article = user.goose_send(request_builder.form(&params), None).await?;

// A successful node save is redirected.
if !saved_article.request.redirected {
return user.set_failure(
&format!("{}: saving article failed", saved_article.request.final_url),
&mut saved_article.request,
Some(&headers),
None,
);
}
}
Err(e) => {
return user.set_failure(
&format!("{}: failed to parse page: {}", goose.request.url, e),
&mut goose.request,
Some(&headers),
None,
);
}
}
}
Err(e) => {
return user.set_failure(
&format!("{}: no response from server: {}", goose.request.url, e),
&mut goose.request,
None,
None,
);
}
}
// Be sure we're viewing the same article after editing it.
common::validate_and_load_static_assets(user, saved_article, article.unwrap().title_en).await?;

Ok(())
}
21 changes: 19 additions & 2 deletions examples/umami/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod admin;
mod common;
mod english;
mod spanish;

use goose::prelude::*;

use crate::admin::*;
use crate::english::*;
use crate::spanish::*;

Expand All @@ -14,7 +16,8 @@ fn main() -> Result<(), GooseError> {
let _goose_metrics = GooseAttack::initialize()?
.register_taskset(
taskset!("Anonymous English user")
.set_weight(6)?
.set_weight(40)?
.set_wait_time(0, 3)?
.register_task(task!(front_page_en).set_name("anon /").set_weight(2)?)
.register_task(task!(basic_page_en).set_name("anon /en/basicpage"))
.register_task(task!(article_listing_en).set_name("anon /en/articles/"))
Expand All @@ -40,7 +43,8 @@ fn main() -> Result<(), GooseError> {
)
.register_taskset(
taskset!("Anonymous Spanish user")
.set_weight(2)?
.set_weight(9)?
.set_wait_time(0, 3)?
.register_task(task!(front_page_es).set_name("anon /es/").set_weight(2)?)
.register_task(task!(basic_page_es).set_name("anon /es/basicpage"))
.register_task(task!(article_listing_es).set_name("anon /es/articles/"))
Expand All @@ -63,6 +67,19 @@ fn main() -> Result<(), GooseError> {
.register_task(task!(search_es).set_name("anon /es/search"))
.register_task(task!(anonymous_contact_form_es).set_name("anon /es/contact")),
)
.register_taskset(
taskset!("Admin user")
.set_weight(1)?
.set_wait_time(3, 10)?
.register_task(task!(log_in).set_on_start().set_name("auth /en/user/login"))
.register_task(task!(front_page_en).set_name("auth /").set_weight(2)?)
.register_task(task!(article_listing_en).set_name("auth /en/articles/"))
.register_task(
task!(edit_article)
.set_name("auth /en/node/%/edit")
.set_weight(2)?,
),
)
.set_default(GooseDefault::Host, "https://drupal-9.0.7.ddev.site/")?
.execute()?
.print();
Expand Down

0 comments on commit 091faa7

Please sign in to comment.