Install script fixes, moved SMTP management API to JMAP listener.

This commit is contained in:
mdecimus 2023-07-14 16:38:13 +02:00
parent 81a5c2f9bf
commit c1ae11c84b
15 changed files with 212 additions and 116 deletions

View file

@ -5,7 +5,7 @@ on:
pull_request:
push:
tags:
- '*'
- "v*.*.*"
jobs:
build:
@ -240,67 +240,113 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docker-amd:
name: Build Docker AMD64 images
if: '!cancelled()'
build_docker:
name: Build Docker image for ${{ matrix.platform }}
runs-on: ubuntu-latest
if: '!cancelled()'
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- name: Checkout
-
name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
-
name: Build and push by digest
id: build
uses: docker/build-push-action@v4
with:
context: .
push: true
platforms: linux/amd64
tags: stalwartlabs/mail-server:latest
cache-from: type=registry,ref=stalwartlabs/mail-server:buildcache
cache-to: type=registry,ref=stalwartlabs/mail-server:buildcache,mode=max
#cache-from: type=gha
#cache-to: type=gha,mode=max
docker-arm:
name: Build Docker ARM64 images
if: '!cancelled()'
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
#cache-from: type=registry,ref=${{ env.REGISTRY_IMAGE }}:buildcache-${{ env.PLATFORM_PAIR }}
#cache-to: type=registry,ref=s${{ env.REGISTRY_IMAGE }}:buildcache-${{ env.PLATFORM_PAIR }},mode=max
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
-
name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
-
name: Upload digest
uses: actions/upload-artifact@v3
with:
name: digests
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge_docker:
name: Merge and push Docker manifest
runs-on: ubuntu-latest
needs:
- build_docker
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
-
name: Download digests
uses: actions/download-artifact@v3
with:
name: digests
path: /tmp/digests
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY_IMAGE }}
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
push: true
platforms: linux/arm64
tags: stalwartlabs/mail-server:latest
cache-from: type=registry,ref=stalwartlabs/mail-server:buildcache
cache-to: type=registry,ref=stalwartlabs/mail-server:buildcache,mode=max
#cache-from: type=gha
#cache-to: type=gha,mode=max
-
name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
-
name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}

View file

@ -15,6 +15,16 @@ try each of these servers individually:
* [Stalwart IMAP Server](https://github.com/stalwartlabs/imap-server/)
* [Stalwart SMTP Server](https://github.com/stalwartlabs/smtp-server/)
## Why choose Stalwart?
Within the field of mail servers, established names like Postfix, Courier and Dovecot have long been the go-to solutions. However, the landscape of internet messaging is evolving, with a need for more efficient, easy to maintain, reliable, and secure systems. Here's why you might consider making the switch to Stalwart Mail Server:
- Designed with the latest internet messaging protocols in mind - JMAP and IMAP4rev2, along with the conventional SMTP.
- Leverages the performance and security benefits of the Rust programming language. This statically typed, compiled language is known for its memory safety and concurrency support, reducing the likelihood of typical security issues like buffer overflows.
- Thanks to its native FoundationDB and S3 storage support, it can be scaled across many servers, accommodating millions of users.
- Available as a single, integrated package that includes JMAP, IMAP, and SMTP servers. This means that you don't have to install, configure and maintain multiple servers to get a complete solution.
- Designed to be easy to install and maintain, with a single configuration file and a simple command-line interface.
## License
Licensed under the terms of the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.en.html) as published by

View file

@ -50,9 +50,9 @@ async fn main() -> std::io::Result<()> {
let is_jmap = args.command.is_jmap();
let credentials = if let Some(credentials) = args.credentials {
parse_credentials(&credentials)
} else if is_jmap {
} else {
let credentials = rpassword::prompt_password(
"\nEnter JMAP admin credentials or press [ENTER] to use OAuth: ",
"\nEnter administrator credentials or press [ENTER] to use OAuth: ",
)
.unwrap();
if !credentials.is_empty() {
@ -60,8 +60,6 @@ async fn main() -> std::io::Result<()> {
} else {
oauth(&args.url).await
}
} else {
parse_credentials(&rpassword::prompt_password("\nEnter SMTP admin credentials: ").unwrap())
};
if is_jmap {

View file

@ -31,7 +31,7 @@ use serde::Deserialize;
pub struct Cli {
#[clap(subcommand)]
pub command: Commands,
/// JMAP or SMTP server base URL
/// Server base URL
#[clap(short, long)]
pub url: String,
/// Authentication credentials

View file

@ -100,7 +100,7 @@ pub async fn cmd_queue(url: &str, credentials: Credentials, command: QueueComman
.collect(),
));
for (message, id) in smtp_manage_request::<Vec<Option<Message>>>(
&build_query(url, "/queue/status?ids=", chunk),
&build_query(url, "/admin/queue/status?ids=", chunk),
&credentials,
)
.await
@ -172,7 +172,7 @@ pub async fn cmd_queue(url: &str, credentials: Credentials, command: QueueComman
}
QueueCommands::Status { ids } => {
for (message, id) in smtp_manage_request::<Vec<Option<Message>>>(
&build_query(url, "/queue/status?ids=", &parse_ids(&ids)),
&build_query(url, "/admin/queue/status?ids=", &parse_ids(&ids)),
&credentials,
)
.await
@ -311,7 +311,7 @@ pub async fn cmd_queue(url: &str, credentials: Credentials, command: QueueComman
std::process::exit(1);
}
let mut query = form_urlencoded::Serializer::new(format!("{url}/queue/retry?"));
let mut query = form_urlencoded::Serializer::new(format!("{url}/admin/queue/retry?"));
if let Some(filter) = &domain {
query.append_pair("filter", filter);
@ -365,7 +365,7 @@ pub async fn cmd_queue(url: &str, credentials: Credentials, command: QueueComman
std::process::exit(1);
}
let mut query = form_urlencoded::Serializer::new(format!("{url}/queue/cancel?"));
let mut query = form_urlencoded::Serializer::new(format!("{url}/admin/queue/cancel?"));
if let Some(filter) = &rcpt {
query.append_pair("filter", filter);
@ -443,7 +443,7 @@ async fn query_messages(
before: &Option<DateTime>,
after: &Option<DateTime>,
) -> Vec<u64> {
let mut query = form_urlencoded::Serializer::new(format!("{url}/queue/list?"));
let mut query = form_urlencoded::Serializer::new(format!("{url}/admin/queue/list?"));
if let Some(sender) = from {
query.append_pair("from", sender);

View file

@ -50,7 +50,7 @@ pub async fn cmd_report(url: &str, credentials: Credentials, command: ReportComm
page_size,
} => {
let stdout = Term::buffered_stdout();
let mut query = form_urlencoded::Serializer::new(format!("{url}/report/list?"));
let mut query = form_urlencoded::Serializer::new(format!("{url}/admin/report/list?"));
if let Some(domain) = &domain {
query.append_pair("domain", domain);
@ -73,7 +73,7 @@ pub async fn cmd_report(url: &str, credentials: Credentials, command: ReportComm
.collect(),
));
for (report, id) in smtp_manage_request::<Vec<Option<Report>>>(
&format!("{url}/report/status?ids={}", chunk.join(",")),
&format!("{url}/admin/report/status?ids={}", chunk.join(",")),
&credentials,
)
.await
@ -110,7 +110,7 @@ pub async fn cmd_report(url: &str, credentials: Credentials, command: ReportComm
}
ReportCommands::Status { ids } => {
for (report, id) in smtp_manage_request::<Vec<Option<Report>>>(
&format!("{url}/report/status?ids={}", ids.join(",")),
&format!("{url}/admin/report/status?ids={}", ids.join(",")),
&credentials,
)
.await
@ -164,7 +164,7 @@ pub async fn cmd_report(url: &str, credentials: Credentials, command: ReportComm
let mut success_count = 0;
let mut failed_list = vec![];
for (success, id) in smtp_manage_request::<Vec<bool>>(
&format!("{url}/report/cancel?ids={}", ids.join(",")),
&format!("{url}/admin/report/cancel?ids={}", ids.join(",")),
&credentials,
)
.await

View file

@ -363,15 +363,20 @@ fn main() -> std::io::Result<()> {
// Obtain TLS certificate path
let (cert_path, pk_path) = if !args.docker {
#[cfg(not(target_env = "msvc"))]
let cert_base_path = format!("/etc/letsencrypt/live/{}/", hostname);
#[cfg(target_env = "msvc")]
let cert_base_path = format!("C:\\Program Files\\Letsencrypt\\live\\{}\\", hostname);
(
input(
&format!("Where is the TLS certificate for '{hostname}' located?"),
&format!("/etc/letsencrypt/live/{hostname}/fullchain.pem"),
&format!("{cert_base_path}fullchain.pem"),
file_exists,
)?,
input(
&format!("Where is the TLS private key for '{hostname}' located?"),
&format!("/etc/letsencrypt/live/{hostname}/privkey.pem"),
&format!("{cert_base_path}privkey.pem"),
file_exists,
)?,
)
@ -827,12 +832,20 @@ impl SelectItem for Blob {
impl Component {
fn default_base_path(&self) -> &'static str {
#[cfg(not(target_env = "msvc"))]
match self {
Self::AllInOne => "/opt/stalwart-mail",
Self::Jmap => "/opt/stalwart-jmap",
Self::Imap => "/opt/stalwart-imap",
Self::Smtp => "/opt/stalwart-smtp",
}
#[cfg(target_env = "msvc")]
match self {
Self::AllInOne => "C:\\Program Files\\Stalwart Mail",
Self::Jmap => "C:\\Program Files\\Stalwart JMAP",
Self::Imap => "C:\\Program Files\\Stalwart IMAP",
Self::Smtp => "C:\\Program Files\\Stalwart SMTP",
}
}
fn binary_name(&self) -> &'static str {

View file

@ -339,6 +339,12 @@ pub async fn parse_jmap_request(
.into_http_response(),
};
}
(path_1 @ ("queue" | "report"), path_2, &Method::GET) => {
return jmap
.smtp
.handle_manage_request(req.uri(), req.method(), path_1, path_2)
.await;
}
_ => (),
}
}

View file

@ -27,7 +27,7 @@ use directory::config::ConfigDirectory;
use imap::core::{ImapSessionManager, IMAP};
use jmap::{api::JmapSessionManager, services::IPC_CHANNEL_BUFFER, JMAP};
use managesieve::core::ManageSieveSessionManager;
use smtp::core::{SmtpAdminSessionManager, SmtpSessionManager, SMTP};
use smtp::core::{SmtpSessionManager, SMTP};
use tokio::sync::mpsc;
use utils::{
config::{Config, ServerProtocol},
@ -79,7 +79,7 @@ async fn main() -> std::io::Result<()> {
server.spawn(SmtpSessionManager::new(smtp.clone()), shutdown_rx)
}
ServerProtocol::Http => {
server.spawn(SmtpAdminSessionManager::new(smtp.clone()), shutdown_rx)
tracing::debug!("Ignoring HTTP server listener, using JMAP port instead.");
}
ServerProtocol::Jmap => {
server.spawn(JmapSessionManager::new(jmap.clone()), shutdown_rx)

View file

@ -30,7 +30,7 @@ use hyper::{
header::{self, AUTHORIZATION},
server::conn::http1,
service::service_fn,
Method, StatusCode,
Method, StatusCode, Uri,
};
use mail_parser::{decoders::base64::base64_decode, DateTime};
use mail_send::Credentials;
@ -312,15 +312,33 @@ impl SMTP {
let mut path = req.uri().path().split('/');
path.next();
let (status, response) = match (req.method(), path.next(), path.next()) {
(&Method::GET, Some("queue"), Some("list")) => {
path.next(); // Skip the leading /admin
Ok(self
.handle_manage_request(
req.uri(),
req.method(),
path.next().unwrap_or_default(),
path.next().unwrap_or_default(),
)
.await)
}
pub async fn handle_manage_request(
&self,
uri: &Uri,
method: &Method,
path_1: &str,
path_2: &str,
) -> hyper::Response<BoxBody<Bytes, hyper::Error>> {
let (status, response) = match (method, path_1, path_2) {
(&Method::GET, "queue", "list") => {
let mut from = None;
let mut to = None;
let mut before = None;
let mut after = None;
let mut error = None;
if let Some(query) = req.uri().query() {
if let Some(query) = uri.query() {
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
match key.as_ref() {
"from" => {
@ -373,11 +391,11 @@ impl SMTP {
Some(error) => error.into_bad_request(),
}
}
(&Method::GET, Some("queue"), Some("status")) => {
(&Method::GET, "queue", "status") => {
let mut queue_ids = Vec::new();
let mut error = None;
if let Some(query) = req.uri().query() {
if let Some(query) = uri.query() {
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
match key.as_ref() {
"id" | "ids" => match value.parse_queue_ids() {
@ -412,13 +430,13 @@ impl SMTP {
Some(error) => error.into_bad_request(),
}
}
(&Method::GET, Some("queue"), Some("retry")) => {
(&Method::GET, "queue", "retry") => {
let mut queue_ids = Vec::new();
let mut time = Instant::now();
let mut item = None;
let mut error = None;
if let Some(query) = req.uri().query() {
if let Some(query) = uri.query() {
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
match key.as_ref() {
"id" | "ids" => match value.parse_queue_ids() {
@ -467,12 +485,12 @@ impl SMTP {
Some(error) => error.into_bad_request(),
}
}
(&Method::GET, Some("queue"), Some("cancel")) => {
(&Method::GET, "queue", "cancel") => {
let mut queue_ids = Vec::new();
let mut item = None;
let mut error = None;
if let Some(query) = req.uri().query() {
if let Some(query) = uri.query() {
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
match key.as_ref() {
"id" | "ids" => match value.parse_queue_ids() {
@ -511,12 +529,12 @@ impl SMTP {
Some(error) => error.into_bad_request(),
}
}
(&Method::GET, Some("report"), Some("list")) => {
(&Method::GET, "report", "list") => {
let mut domain = None;
let mut type_ = None;
let mut error = None;
if let Some(query) = req.uri().query() {
if let Some(query) = uri.query() {
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
match key.as_ref() {
"type" => match value.as_ref() {
@ -558,11 +576,11 @@ impl SMTP {
Some(error) => error.into_bad_request(),
}
}
(&Method::GET, Some("report"), Some("status")) => {
(&Method::GET, "report", "status") => {
let mut report_ids = Vec::new();
let mut error = None;
if let Some(query) = req.uri().query() {
if let Some(query) = uri.query() {
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
match key.as_ref() {
"id" | "ids" => match value.parse_report_ids() {
@ -597,11 +615,11 @@ impl SMTP {
Some(error) => error.into_bad_request(),
}
}
(&Method::GET, Some("report"), Some("cancel")) => {
(&Method::GET, "report", "cancel") => {
let mut report_ids = Vec::new();
let mut error = None;
if let Some(query) = req.uri().query() {
if let Some(query) = uri.query() {
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
match key.as_ref() {
"id" | "ids" => match value.parse_report_ids() {
@ -640,12 +658,12 @@ impl SMTP {
StatusCode::NOT_FOUND,
format!(
"{{\"error\": \"not-found\", \"details\": \"URL {} does not exist.\"}}",
req.uri().path()
uri.path()
),
),
};
Ok(hyper::Response::builder()
hyper::Response::builder()
.status(status)
.header(header::CONTENT_TYPE, "application/json; charset=utf-8")
.body(
@ -653,7 +671,7 @@ impl SMTP {
.map_err(|never| match never {})
.boxed(),
)
.unwrap())
.unwrap()
}
async fn send_queue_event<T: Serialize>(

View file

@ -1,7 +1,7 @@
#!/usr/bin/env sh
# shellcheck shell=dash
# Stalwart SMTP install script -- based on the rustup installation script.
# Stalwart Mail install script -- based on the rustup installation script.
set -e
set -u
@ -67,14 +67,14 @@ main() {
ensure dscl /Local/Default -create Groups/_stalwart-mail
ensure dscl /Local/Default -create Groups/_stalwart-mail Password \*
ensure dscl /Local/Default -create Groups/_stalwart-mail PrimaryGroupID $_gid
ensure dscl /Local/Default -create Groups/_stalwart-mail RealName "Stalwart SMTP service"
ensure dscl /Local/Default -create Groups/_stalwart-mail RealName "Stalwart Mail service"
ensure dscl /Local/Default -create Groups/_stalwart-mail RecordName _stalwart-mail stalwart-mail
ensure dscl /Local/Default -create Users/_stalwart-mail
ensure dscl /Local/Default -create Users/_stalwart-mail NFSHomeDirectory /Users/_stalwart-mail
ensure dscl /Local/Default -create Users/_stalwart-mail Password \*
ensure dscl /Local/Default -create Users/_stalwart-mail PrimaryGroupID $_gid
ensure dscl /Local/Default -create Users/_stalwart-mail RealName "Stalwart SMTP service"
ensure dscl /Local/Default -create Users/_stalwart-mail RealName "Stalwart Mail service"
ensure dscl /Local/Default -create Users/_stalwart-mail RecordName _stalwart-mail stalwart-mail
ensure dscl /Local/Default -create Users/_stalwart-mail UniqueID $_uid
ensure dscl /Local/Default -create Users/_stalwart-mail UserShell /bin/bash

View file

@ -64,9 +64,9 @@ connect-timeout = "30s"
[directory."ldap".filter]
name = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(uid=?))"
email = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=?)(mailAlias=?)))"
email = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=?)(mailAlias=?)(mailList=?)))"
verify = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*?*)(mailAlias=*?*)))"
expand = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(sn=?))"
expand = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(mailList=?))"
domains = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*@?)(mailAlias=*@?)))"
[directory."ldap".object-classes]

View file

@ -74,10 +74,6 @@ relay = [ { if = "authenticated-as", ne = "", then = true },
max-recipients = 25
directory = "__SMTP_DIRECTORY__"
[session.rcpt.cache]
entries = 1000
ttl = {positive = 10, negative = 5}
[session.rcpt.errors]
total = 5
wait = "5s"
@ -218,10 +214,10 @@ mta-sts = 1024
[report]
path = "__PATH__/reports"
hash = 64
#submitter = "mx.domain.org"
#submitter = "__HOST__"
[report.analysis]
addresses = ["dmarc@*", "abuse@*"]
addresses = ["dmarc@*", "abuse@*", "postmaster@*"]
forward = true
#store = "__PATH__/incoming"

View file

@ -183,7 +183,7 @@ async fn manage_queue() {
);
// Fetch and validate messages
let ids = send_manage_request::<Vec<QueueId>>("/queue/list")
let ids = send_manage_request::<Vec<QueueId>>("/admin/queue/list")
.await
.unwrap()
.unwrap_data();
@ -264,15 +264,24 @@ async fn manage_queue() {
// Test list search
for (query, expected_ids) in [
("/queue/list?from=bill1@foobar.net".to_string(), vec!["a"]),
("/queue/list?to=foobar.org".to_string(), vec!["d", "e", "f"]),
(
"/queue/list?from=bill3@foobar.net&to=rcpt5@example1.com".to_string(),
"/admin/queue/list?from=bill1@foobar.net".to_string(),
vec!["a"],
),
(
"/admin/queue/list?to=foobar.org".to_string(),
vec!["d", "e", "f"],
),
(
"/admin/queue/list?from=bill3@foobar.net&to=rcpt5@example1.com".to_string(),
vec!["c"],
),
(format!("/queue/list?before={test_search}"), vec!["a", "b"]),
(
format!("/queue/list?after={test_search}"),
format!("/admin/queue/list?before={test_search}"),
vec!["a", "b"],
),
(
format!("/admin/queue/list?after={test_search}"),
vec!["d", "e", "f", "c"],
),
] {
@ -290,7 +299,7 @@ async fn manage_queue() {
// Retry delivery
assert_eq!(
send_manage_request::<Vec<bool>>(&format!(
"/queue/retry?id={},{}",
"/admin/queue/retry?id={},{}",
id_map.get("e").unwrap(),
id_map.get("f").unwrap()
))
@ -301,7 +310,7 @@ async fn manage_queue() {
);
assert_eq!(
send_manage_request::<Vec<bool>>(&format!(
"/queue/retry?id={}&filter=example1.org&at=2200-01-01T00:00:00Z",
"/admin/queue/retry?id={}&filter=example1.org&at=2200-01-01T00:00:00Z",
id_map.get("a").unwrap(),
))
.await
@ -365,7 +374,7 @@ async fn manage_queue() {
] {
assert_eq!(
send_manage_request::<Vec<bool>>(&format!(
"/queue/cancel?id={}{}{}",
"/admin/queue/cancel?id={}{}{}",
id_map.get(id).unwrap(),
if !filter.is_empty() { "&filter=" } else { "" },
filter
@ -378,7 +387,7 @@ async fn manage_queue() {
);
}
assert_eq!(
send_manage_request::<Vec<QueueId>>("/queue/list")
send_manage_request::<Vec<QueueId>>("/admin/queue/list")
.await
.unwrap()
.unwrap_data()
@ -464,7 +473,7 @@ fn assert_timestamp(timestamp: &DateTime, expected: i64, ctx: &str, message: &Me
async fn get_messages(ids: &[QueueId]) -> Vec<Option<Message>> {
send_manage_request(&format!(
"/queue/status?id={}",
"/admin/queue/status?id={}",
ids.iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()

View file

@ -141,7 +141,7 @@ async fn manage_reports() {
.await;
// List reports
let ids = send_manage_request::<Vec<String>>("/report/list")
let ids = send_manage_request::<Vec<String>>("/admin/report/list")
.await
.unwrap()
.unwrap_data();
@ -172,12 +172,12 @@ async fn manage_reports() {
// Test list search
for (query, expected_ids) in [
("/report/list?type=dmarc", vec!["a", "b"]),
("/report/list?type=tls", vec!["c", "d"]),
("/report/list?domain=foobar.org", vec!["a", "c"]),
("/report/list?domain=foobar.net", vec!["b", "d"]),
("/report/list?domain=foobar.org&type=dmarc", vec!["a"]),
("/report/list?domain=foobar.net&type=tls", vec!["d"]),
("/admin/report/list?type=dmarc", vec!["a", "b"]),
("/admin/report/list?type=tls", vec!["c", "d"]),
("/admin/report/list?domain=foobar.org", vec!["a", "c"]),
("/admin/report/list?domain=foobar.net", vec!["b", "d"]),
("/admin/report/list?domain=foobar.org&type=dmarc", vec!["a"]),
("/admin/report/list?domain=foobar.net&type=tls", vec!["d"]),
] {
let expected_ids = HashSet::from_iter(expected_ids.into_iter().map(|s| s.to_string()));
let ids = send_manage_request::<Vec<String>>(query)
@ -194,7 +194,7 @@ async fn manage_reports() {
for id in ["a", "b"] {
assert_eq!(
send_manage_request::<Vec<bool>>(&format!(
"/report/cancel?id={}",
"/admin/report/cancel?id={}",
id_map.get(id).unwrap(),
))
.await
@ -205,7 +205,7 @@ async fn manage_reports() {
);
}
assert_eq!(
send_manage_request::<Vec<String>>("/report/list")
send_manage_request::<Vec<String>>("/admin/report/list")
.await
.unwrap()
.unwrap_data()
@ -227,7 +227,7 @@ async fn manage_reports() {
}
async fn get_reports(ids: &[String]) -> Vec<Option<Report>> {
send_manage_request(&format!("/report/status?id={}", ids.join(",")))
send_manage_request(&format!("/admin/report/status?id={}", ids.join(",")))
.await
.unwrap()
.unwrap_data()