Compare commits

..

No commits in common. "main" and "v0.5.0" have entirely different histories.
main ... v0.5.0

18 changed files with 451 additions and 846 deletions

View file

@ -1,7 +0,0 @@
*
!Cargo.toml
!Cargo.lock
!core/
!server/
!sqlite/
!docker-entrypoint.sh

58
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,58 @@
name: Build
on: [push, pull_request]
jobs:
build:
strategy:
fail-fast: false
matrix:
target:
- tag: amd64-musl
target: x86_64-unknown-linux-musl
- tag: amd64-glibc
target: x86_64-unknown-linux-gnu
name: Build TaskChampion Sync-Server ${{ matrix.target.tag }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Load .env file
uses: xom9ikk/dotenv@v2
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_VERSION }}
targets: ${{ matrix.target.target }}
- name: Build
run: |
[ "${{ matrix.target.target }}" == "x86_64-unknown-linux-musl" ] && sudo apt update && sudo apt -y install musl-tools
cargo build --target ${{ matrix.target.target }} --release --locked
- name: Package current compilation
id: package-current
run: |
install -Dm755 "target/${{ matrix.target.target }}/release/taskchampion-sync-server" "taskchampion-sync-server-${{ matrix.target.tag }}-${GITHUB_REF##*/}-${GITHUB_SHA}/taskchampion-sync-server"
install -Dm644 "README.md" "taskchampion-sync-server-${{ matrix.target.tag }}-${GITHUB_REF##*/}-${GITHUB_SHA}/README.md"
install -Dm644 "LICENSE" "taskchampion-sync-server-${{ matrix.target.tag }}-${GITHUB_REF##*/}-${GITHUB_SHA}/LICENSE"
echo "version=${GITHUB_REF##*/}-${GITHUB_SHA}" >> $GITHUB_OUTPUT
- name: Archive current compilation
uses: actions/upload-artifact@v4
with:
name: "taskchampion-sync-server-${{ matrix.target.tag }}-${{ steps.package-current.outputs.version }}"
path: "taskchampion-sync-server-${{ matrix.target.tag }}-${{ steps.package-current.outputs.version }}/"
- name: Package tagged compilation
id: package
if: startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request'
run: |
install -Dm755 "target/${{ matrix.target.target }}/release/taskchampion-sync-server" "taskchampion-sync-server-${{ matrix.target.tag }}-${GITHUB_REF##*/}/taskchampion-sync-server"
install -Dm644 "README.md" "taskchampion-sync-server-${{ matrix.target.tag }}-${GITHUB_REF##*/}/README.md"
install -Dm644 "LICENSE" "taskchampion-sync-server-${{ matrix.target.tag }}-${GITHUB_REF##*/}/LICENSE"
tar cvJf "taskchampion-sync-server-${{ matrix.target.tag }}-${GITHUB_REF##*/}.tar.xz" "taskchampion-sync-server-${{ matrix.target.tag }}-${GITHUB_REF##*/}"
echo "version=${GITHUB_REF##*/}" >> $GITHUB_OUTPUT
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request'
with:
files: "taskchampion-sync-server-${{ matrix.target.tag }}-${{ steps.package.outputs.version }}.tar.xz"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -2,6 +2,8 @@ name: Build Docker
on:
push:
branches:
- '*'
tags:
- '*'

View file

@ -13,8 +13,6 @@ jobs:
# A simple matrix for now, but if we introduce an MSRV it can be added here.
matrix:
rust:
# MSRV
- "1.81.0"
- "stable"
runs-on: ubuntu-latest

View file

@ -2,7 +2,7 @@ name: security
on:
schedule:
- cron: '33 0 * * THU'
- cron: '0 0 * * *'
push:
paths:
- '**/Cargo.toml'

708
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,22 +5,20 @@ members = [
"server",
"sqlite",
]
rust-version = "1.81.0" # MSRV
[workspace.dependencies]
uuid = { version = "^1.17.0", features = ["serde", "v4"] }
actix-web = "^4.11.0"
uuid = { version = "^1.11.0", features = ["serde", "v4"] }
actix-web = "^4.9.0"
anyhow = "1.0"
thiserror = "2.0"
futures = "^0.3.25"
serde_json = "^1.0"
serde = { version = "^1.0.147", features = ["derive"] }
clap = { version = "^4.5.6", features = ["string", "env"] }
clap = { version = "^4.5.6", features = ["string"] }
log = "^0.4.17"
env_logger = "^0.11.7"
env_logger = "^0.11.5"
rusqlite = { version = "0.32", features = ["bundled"] }
chrono = { version = "^0.4.38", features = ["serde"] }
actix-rt = "2"
tempfile = "3"
pretty_assertions = "1"
temp-env = "0.3"

View file

@ -1,25 +1,19 @@
# Versions must be major.minor
# Default versions are as below
ARG RUST_VERSION=1.78
ARG ALPINE_VERSION=3.19
ARG RUST_VERSION
ARG ALPINE_VERSION
FROM docker.io/rust:${RUST_VERSION}-alpine${ALPINE_VERSION} AS builder
COPY Cargo.lock Cargo.toml /data/
COPY core /data/core/
COPY server /data/server/
COPY sqlite /data/sqlite/
COPY . /data
RUN apk -U add libc-dev && \
cd /data && \
cargo build --release
FROM docker.io/alpine:${ALPINE_VERSION}
COPY --from=builder /data/target/release/taskchampion-sync-server /bin
RUN apk add --no-cache su-exec && \
adduser -u 1092 -S -D -H -h /var/lib/taskchampion-sync-server -s /sbin/nologin -G users \
RUN adduser -S -D -H -h /var/lib/taskchampion-sync-server -s /sbin/nologin -G users \
-g taskchampion taskchampion && \
install -d -m1755 -o1092 -g1092 "/var/lib/taskchampion-sync-server"
install -d -m755 -o100 -g100 "/var/lib/taskchampion-sync-server"
EXPOSE 8080
VOLUME /var/lib/taskchampion-sync-server/data
COPY docker-entrypoint.sh /bin
ENTRYPOINT [ "/bin/docker-entrypoint.sh" ]
CMD [ "/bin/taskchampion-sync-server" ]
VOLUME "/var/lib/taskchampion-sync-server"
USER taskchampion
ENTRYPOINT [ "taskchampion-sync-server" ]

View file

@ -27,42 +27,31 @@ use a reverse proxy such as Nginx, haproxy, or Apache httpd.
### Using Docker-Compose
Every release of the server generates a Docker image in
`ghcr.io/gothenburgbitfactory/taskchampion-sync-server`. The tags include
`latest` for the latest release, and both minor and patch versions, e.g., `0.5`
and `0.5.1`.
The
[`docker-compose.yml`](https://raw.githubusercontent.com/GothenburgBitFactory/taskchampion-sync-server/refs/tags/v0.6.1/docker-compose.yml)
file in this repository is sufficient to run taskchampion-sync-server,
including setting up TLS certificates using Lets Encrypt, thanks to
[Caddy](https://caddyserver.com/).
The [`docker-compose.yml`](./docker-compose.yml) file in this repository is
sufficient to run taskchampion-sync-server, including setting up TLS
certificates using Lets Encrypt, thanks to [Caddy](https://caddyserver.com/).
You will need a server with ports 80 and 443 open to the Internet and with a
fixed, publicly-resolvable hostname. These ports must be available both to your
Taskwarrior clients and to the Lets Encrypt servers.
On that server, download `docker-compose.yml` from the link above (it is pinned
to the latest release) into the current directory. Then run
On that server, clone this repository (or just download `docker-compose.yml` to
the current directory -- the rest of the contents of this repository are not
required) and run
```sh
TASKCHAMPION_SYNC_SERVER_HOSTNAME=taskwarrior.example.com \
TASKCHAMPION_SYNC_SERVER_CLIENT_ID=your-client-id \
docker compose up
TASKCHAMPION_SYNC_SERVER_HOSTNAME=taskwarrior.example.com docker compose up
```
The `TASKCHAMPION_SYNC_SERVER_CLIENT_ID` limits the server to the given client
ID; omit it to allow all client IDs.
It can take a few minutes to obtain the certificate; the caddy container will
log a message "certificate obtained successfully" when this is complete, or
error messages if the process fails. Once this process is complete, configure
your `.taskrc`'s to point to the server:
log a msg "certificate obtained successfully" when this is complete, or error
messages if the process fails. Once this process is complete, configure your
`.taskrc`'s to point to the server:
```
sync.server.url=https://taskwarrior.example.com
sync.server.client_id=your-client-id
sync.encryption_secret=your-encryption-secret
sync.server.client_id=[your client-id]
sync.encryption_secret=[your encryption secret]
```
The docker-compose images store data in a docker volume named
@ -76,20 +65,11 @@ system startup. See the docker-compose documentation for more information.
The server is configured with command-line options. See
`taskchampion-sync-server --help` for full details.
The `--listen` option specifies the interface and port the server listens on.
It must contain an IP-Address or a DNS name and a port number. This option is
mandatory, but can be repeated to specify multiple interfaces or ports. This
value can be specified in environment variable `LISTEN`, as a comma-separated
list of values.
The `--data-dir` option specifies where the server should store its data. This
value can be specified in the environment variable `DATA_DIR`.
The `--data-dir` option specifies where the server should store its data, and
`--port` gives the port on which the HTTP server runs.
By default, the server allows all client IDs. To limit the accepted client IDs,
specify them in the environment variable `CLIENT_ID`, as a comma-separated list
of UUIDs. Client IDs can be specified with `--allow-client-id`, but this should
not be used on shared systems, as command line arguments are visible to all
users on the system.
such as when running a personal server, use `--allow-client-id <client-id>`.
The server only logs errors by default. To add additional logging output, set
environment variable `RUST_LOG` to `info` to get a log message for every
@ -108,11 +88,7 @@ release version. You can install Rust from your distribution package or use
rustup default stable
```
The minimum supported Rust version (MSRV) is given in
[`Cargo.toml`](./Cargo.toml). Note that package repositories typically do not
have sufficiently new versions of Rust.
If you prefer, you can use the stable version only for installing TaskChampion
If you prefer, you can use the stable version only for install TaskChampion
Sync-Server (you must clone the repository first).
```sh
rustup override set stable
@ -132,7 +108,6 @@ cargo build --release
After build the binary is located in
`target/release/taskchampion-sync-server`.
### Building the Container
To build the container execute the following commands.
@ -154,12 +129,4 @@ docker run -t -d \
This start TaskChampion Sync-Server and publish the port to host. Please
note that this is a basic run, all data will be destroyed after stop and
delete container. You may also set `DATA_DIR`, `CLIENT_ID`, or `LISTEN` with `-e`, e.g.,
```sh
docker run -t -d \
--name=taskchampion \
-e LISTEN=0.0.0.0:9000 \
-p 9000:9000 \
taskchampion-sync-server
```
delete container.

View file

@ -1,21 +0,0 @@
# Release process
1. Run `git pull upstream main`
1. Run `cargo test`
1. Run `cargo clean && cargo clippy`
1. Remove the `-pre` from `version` in all `*/Cargo.toml`, and from the `version = ..` in any references between packages.
1. Update the link to `docker-compose.yml` in `README.md` to refer to the new version.
1. Update the docker image in `docker-compose.yml` to refer to the new version.
1. Run `cargo semver-checks` (https://crates.io/crates/cargo-semver-checks)
1. Run `cargo build --release`
1. Commit the changes (Cargo.lock will change too) with comment `vX.Y.Z`.
1. Run `git tag vX.Y.Z`
1. Run `git push upstream`
1. Run `git push upstream --tag vX.Y.Z`
1. Run `cargo publish -p taskchampion-sync-server-core`
1. Run `cargo publish -p taskchampion-sync-server-storage-sqlite` (and add any other new published packages here)
1. Bump the patch version in `*/Cargo.toml` and add the `-pre` suffix. This allows `cargo-semver-checks` to check for changes not accounted for in the version delta.
1. Run `cargo build --release` again to update `Cargo.lock`
1. Commit that change with comment "Bump to -pre version".
1. Run `git push upstream`
1. Navigate to the tag in the GitHub releases UI and create a release with general comments about the changes in the release

View file

@ -1,11 +1,9 @@
[package]
name = "taskchampion-sync-server-core"
version = "0.6.2-pre"
version = "0.5.0-pre"
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
edition = "2021"
description = "Core of sync protocol for TaskChampion"
homepage = "https://github.com/GothenburgBitFactory/taskchampion"
repository = "https://github.com/GothenburgBitFactory/taskchampion-sync-server"
license = "MIT"
[dependencies]

View file

@ -130,6 +130,7 @@ impl StorageTxn for InnerTxn<'_> {
parent_version_id: Uuid,
history_segment: Vec<u8>,
) -> anyhow::Result<()> {
// TODO: verify it doesn't exist (`.entry`?)
let version = Version {
version_id,
parent_version_id,
@ -142,33 +143,15 @@ impl StorageTxn for InnerTxn<'_> {
snap.versions_since += 1;
}
} else {
anyhow::bail!("Client {} does not exist", self.client_id);
return Err(anyhow::anyhow!("Client {} does not exist", self.client_id));
}
if self
.guard
self.guard
.children
.insert((self.client_id, parent_version_id), version_id)
.is_some()
{
anyhow::bail!(
"Client {} already has a child for {}",
self.client_id,
parent_version_id
);
}
if self
.guard
.insert((self.client_id, parent_version_id), version_id);
self.guard
.versions
.insert((self.client_id, version_id), version)
.is_some()
{
anyhow::bail!(
"Client {} already has a version {}",
self.client_id,
version_id
);
}
.insert((self.client_id, version_id), version);
self.written = true;
Ok(())
@ -276,25 +259,6 @@ mod test {
Ok(())
}
#[test]
fn test_add_version_exists() -> anyhow::Result<()> {
let storage = InMemoryStorage::new();
let client_id = Uuid::new_v4();
let mut txn = storage.txn(client_id)?;
let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let history_segment = b"abc".to_vec();
txn.new_client(parent_version_id)?;
txn.add_version(version_id, parent_version_id, history_segment.clone())?;
assert!(txn
.add_version(version_id, parent_version_id, history_segment.clone())
.is_err());
txn.commit()?;
Ok(())
}
#[test]
fn test_snapshots() -> anyhow::Result<()> {
let storage = InMemoryStorage::new();

View file

@ -1,13 +1,16 @@
volumes:
data:
services:
# Make the necessary subdirectories of the `data` volume, and set ownership of the
# `tss/taskchampion-sync-server` directory, as the server runs as user 100.
mkdir:
image: caddy:2-alpine
command: |
/bin/sh -c "
mkdir -p /data/caddy/data /data/caddy/config /data/tss/taskchampion-sync-server"
mkdir -p /data/caddy/data /data/caddy/config /data/tss/taskchampion-sync-server &&
chown -R 100:100 /data/tss/taskchampion-sync-server
"
volumes:
- type: volume
source: data
@ -43,21 +46,19 @@ services:
condition: service_completed_successfully
tss:
image: ghcr.io/gothenburgbitfactory/taskchampion-sync-server:0.6.1
image: ghcr.io/gothenburgbitfactory/taskchampion-sync-server:main
restart: unless-stopped
environment:
- "RUST_LOG=info"
- "DATA_DIR=/var/lib/taskchampion-sync-server/data"
- "LISTEN=0.0.0.0:8080"
- "CLIENT_ID=${TASKCHAMPION_SYNC_SERVER_CLIENT_ID}"
volumes:
- type: volume
source: data
target: /var/lib/taskchampion-sync-server/data
target: /tss
read_only: false
volume:
nocopy: true
subpath: tss/taskchampion-sync-server
subpath: tss
command: --data-dir /tss/taskchampion-sync-server --port 8080
environment:
- RUST_LOG=info
depends_on:
mkdir:
condition: service_completed_successfully

View file

@ -1,29 +0,0 @@
#!/bin/sh
set -e
echo "starting entrypoint script..."
if [ "$1" = "/bin/taskchampion-sync-server" ]; then
: ${DATA_DIR:=/var/lib/taskchampion-sync-server}
export DATA_DIR
echo "setting up data directory ${DATA_DIR}"
mkdir -p "${DATA_DIR}"
chown -R taskchampion:users "${DATA_DIR}"
chmod -R 700 "${DATA_DIR}"
: ${LISTEN:=0.0.0.0:8080}
export LISTEN
echo "Listen set to ${LISTEN}"
if [ -n "${CLIENT_ID}" ]; then
export CLIENT_ID
echo "Limiting to client ID ${CLIENT_ID}"
else
unset CLIENT_ID
fi
if [ "$(id -u)" = "0" ]; then
echo "Running server as user 'taskchampion'"
exec su-exec taskchampion "$@"
fi
else
eval "${@}"
fi

View file

@ -1,6 +1,6 @@
[package]
name = "taskchampion-sync-server"
version = "0.6.2-pre"
version = "0.4.1"
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
edition = "2021"
publish = false
@ -24,4 +24,3 @@ chrono.workspace = true
actix-rt.workspace = true
tempfile.workspace = true
pretty_assertions.workspace = true
temp-env.workspace = true

View file

@ -21,38 +21,30 @@ fn command() -> Command {
.version(env!("CARGO_PKG_VERSION"))
.about("Server for TaskChampion")
.arg(
arg!(-l --listen <ADDRESS>)
.help("Address and Port on which to listen on. Can be an IP Address or a DNS name followed by a colon and a port e.g. localhost:8080")
.value_delimiter(',')
.value_parser(ValueParser::string())
.env("LISTEN")
.action(ArgAction::Append)
.required(true),
arg!(-p --port <PORT> "Port on which to serve")
.help("Port on which to serve")
.value_parser(value_parser!(usize))
.default_value("8080"),
)
.arg(
arg!(-d --"data-dir" <DIR> "Directory in which to store data")
.value_parser(ValueParser::os_string())
.env("DATA_DIR")
.default_value("/var/lib/taskchampion-sync-server"),
)
.arg(
arg!(-C --"allow-client-id" <CLIENT_ID> "Client IDs to allow (can be repeated; if not specified, all clients are allowed)")
.value_delimiter(',')
.value_parser(value_parser!(Uuid))
.env("CLIENT_ID")
.action(ArgAction::Append)
.required(false),
)
.arg(
arg!(--"snapshot-versions" <NUM> "Target number of versions between snapshots")
.value_parser(value_parser!(u32))
.env("SNAPSHOT_VERSIONS")
.default_value(default_snapshot_versions),
)
.arg(
arg!(--"snapshot-days" <NUM> "Target number of days between snapshots")
.value_parser(value_parser!(i64))
.env("SNAPSHOT_DAYS")
.default_value(default_snapshot_days),
)
}
@ -64,59 +56,35 @@ fn print_error<B>(res: ServiceResponse<B>) -> actix_web::Result<ErrorHandlerResp
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
}
struct ServerArgs {
data_dir: OsString,
snapshot_versions: u32,
snapshot_days: i64,
client_id_allowlist: Option<HashSet<Uuid>>,
listen_addresses: Vec<String>,
}
impl ServerArgs {
fn new(matches: clap::ArgMatches) -> Self {
Self {
data_dir: matches.get_one::<OsString>("data-dir").unwrap().clone(),
snapshot_versions: *matches.get_one("snapshot-versions").unwrap(),
snapshot_days: *matches.get_one("snapshot-days").unwrap(),
client_id_allowlist: matches
.get_many("allow-client-id")
.map(|ids| ids.copied().collect()),
listen_addresses: matches
.get_many::<String>("listen")
.unwrap()
.cloned()
.collect(),
}
}
}
#[actix_web::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
let matches = command().get_matches();
let server_args = ServerArgs::new(matches);
let config = ServerConfig {
snapshot_days: server_args.snapshot_days,
snapshot_versions: server_args.snapshot_versions,
};
let server = WebServer::new(
config,
server_args.client_id_allowlist,
SqliteStorage::new(server_args.data_dir)?,
);
let data_dir: &OsString = matches.get_one("data-dir").unwrap();
let port: usize = *matches.get_one("port").unwrap();
let snapshot_versions: u32 = *matches.get_one("snapshot-versions").unwrap();
let snapshot_days: i64 = *matches.get_one("snapshot-days").unwrap();
let client_id_allowlist: Option<HashSet<Uuid>> = matches
.get_many("allow-client-id")
.map(|ids| ids.copied().collect());
let mut http_server = HttpServer::new(move || {
let config = ServerConfig {
snapshot_days,
snapshot_versions,
};
let server = WebServer::new(config, client_id_allowlist, SqliteStorage::new(data_dir)?);
log::info!("Serving on port {}", port);
HttpServer::new(move || {
App::new()
.wrap(ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, print_error))
.wrap(Logger::default())
.configure(|cfg| server.config(cfg))
});
for listen_address in server_args.listen_addresses {
log::info!("Serving on {}", listen_address);
http_server = http_server.bind(listen_address)?
}
http_server.run().await?;
})
.bind(format!("0.0.0.0:{}", port))?
.run()
.await?;
Ok(())
}
@ -126,187 +94,55 @@ mod test {
use actix_web::{self, App};
use clap::ArgMatches;
use taskchampion_sync_server_core::InMemoryStorage;
use temp_env::{with_var, with_var_unset, with_vars, with_vars_unset};
/// Get the list of allowed client IDs, sorted.
fn allowed(matches: ArgMatches) -> Option<Vec<Uuid>> {
ServerArgs::new(matches)
.client_id_allowlist
.map(|ids| ids.into_iter().collect::<Vec<_>>())
.map(|mut ids| {
ids.sort();
ids
})
}
#[test]
fn command_listen_two() {
with_var_unset("LISTEN", || {
let matches = command().get_matches_from([
"tss",
"--listen",
"localhost:8080",
"--listen",
"otherhost:9090",
]);
assert_eq!(
ServerArgs::new(matches).listen_addresses,
vec!["localhost:8080".to_string(), "otherhost:9090".to_string()]
);
});
}
#[test]
fn command_listen_two_env() {
with_var("LISTEN", Some("localhost:8080,otherhost:9090"), || {
let matches = command().get_matches_from(["tss"]);
assert_eq!(
ServerArgs::new(matches).listen_addresses,
vec!["localhost:8080".to_string(), "otherhost:9090".to_string()]
);
});
/// Get the list of allowed client IDs
fn allowed(matches: &ArgMatches) -> Option<Vec<Uuid>> {
matches
.get_many::<Uuid>("allow-client-id")
.map(|ids| ids.copied().collect::<Vec<_>>())
}
#[test]
fn command_allowed_client_ids_none() {
with_var_unset("CLIENT_ID", || {
let matches = command().get_matches_from(["tss", "--listen", "localhost:8080"]);
assert_eq!(allowed(matches), None);
});
let matches = command().get_matches_from(["tss"]);
assert_eq!(allowed(&matches), None);
}
#[test]
fn command_allowed_client_ids_one() {
with_var_unset("CLIENT_ID", || {
let matches = command().get_matches_from([
"tss",
"--listen",
"localhost:8080",
"-C",
"711d5cf3-0cf0-4eb8-9eca-6f7f220638c0",
]);
assert_eq!(
allowed(matches),
Some(vec![Uuid::parse_str(
"711d5cf3-0cf0-4eb8-9eca-6f7f220638c0"
)
.unwrap()])
);
});
}
#[test]
fn command_allowed_client_ids_one_env() {
with_var(
"CLIENT_ID",
Some("711d5cf3-0cf0-4eb8-9eca-6f7f220638c0"),
|| {
let matches = command().get_matches_from(["tss", "--listen", "localhost:8080"]);
assert_eq!(
allowed(matches),
Some(vec![Uuid::parse_str(
"711d5cf3-0cf0-4eb8-9eca-6f7f220638c0"
)
.unwrap()])
);
},
let matches =
command().get_matches_from(["tss", "-C", "711d5cf3-0cf0-4eb8-9eca-6f7f220638c0"]);
assert_eq!(
allowed(&matches),
Some(vec![Uuid::parse_str(
"711d5cf3-0cf0-4eb8-9eca-6f7f220638c0"
)
.unwrap()])
);
}
#[test]
fn command_allowed_client_ids_two() {
with_var_unset("CLIENT_ID", || {
let matches = command().get_matches_from([
"tss",
"--listen",
"localhost:8080",
"-C",
"711d5cf3-0cf0-4eb8-9eca-6f7f220638c0",
"-C",
"bbaf4b61-344a-4a39-a19e-8caa0669b353",
]);
assert_eq!(
allowed(matches),
Some(vec![
Uuid::parse_str("711d5cf3-0cf0-4eb8-9eca-6f7f220638c0").unwrap(),
Uuid::parse_str("bbaf4b61-344a-4a39-a19e-8caa0669b353").unwrap()
])
);
});
}
#[test]
fn command_allowed_client_ids_two_env() {
with_var(
"CLIENT_ID",
Some("711d5cf3-0cf0-4eb8-9eca-6f7f220638c0,bbaf4b61-344a-4a39-a19e-8caa0669b353"),
|| {
let matches = command().get_matches_from(["tss", "--listen", "localhost:8080"]);
assert_eq!(
allowed(matches),
Some(vec![
Uuid::parse_str("711d5cf3-0cf0-4eb8-9eca-6f7f220638c0").unwrap(),
Uuid::parse_str("bbaf4b61-344a-4a39-a19e-8caa0669b353").unwrap()
])
);
},
let matches = command().get_matches_from([
"tss",
"-C",
"711d5cf3-0cf0-4eb8-9eca-6f7f220638c0",
"-C",
"bbaf4b61-344a-4a39-a19e-8caa0669b353",
]);
assert_eq!(
allowed(&matches),
Some(vec![
Uuid::parse_str("711d5cf3-0cf0-4eb8-9eca-6f7f220638c0").unwrap(),
Uuid::parse_str("bbaf4b61-344a-4a39-a19e-8caa0669b353").unwrap()
])
);
}
#[test]
fn command_data_dir() {
with_var_unset("DATA_DIR", || {
let matches = command().get_matches_from([
"tss",
"--data-dir",
"/foo/bar",
"--listen",
"localhost:8080",
]);
assert_eq!(ServerArgs::new(matches).data_dir, "/foo/bar");
});
}
#[test]
fn command_data_dir_env() {
with_var("DATA_DIR", Some("/foo/bar"), || {
let matches = command().get_matches_from(["tss", "--listen", "localhost:8080"]);
assert_eq!(ServerArgs::new(matches).data_dir, "/foo/bar");
});
}
#[test]
fn command_snapshot() {
with_vars_unset(["SNAPSHOT_DAYS", "SNAPSHOT_VERSIONS"], || {
let matches = command().get_matches_from([
"tss",
"--listen",
"localhost:8080",
"--snapshot-days",
"13",
"--snapshot-versions",
"20",
]);
let server_args = ServerArgs::new(matches);
assert_eq!(server_args.snapshot_days, 13i64);
assert_eq!(server_args.snapshot_versions, 20u32);
});
}
#[test]
fn command_snapshot_env() {
with_vars(
[
("SNAPSHOT_DAYS", Some("13")),
("SNAPSHOT_VERSIONS", Some("20")),
],
|| {
let matches = command().get_matches_from(["tss", "--listen", "localhost:8080"]);
let server_args = ServerArgs::new(matches);
assert_eq!(server_args.snapshot_days, 13i64);
assert_eq!(server_args.snapshot_versions, 20u32);
},
);
let matches = command().get_matches_from(["tss", "--data-dir", "/foo/bar"]);
assert_eq!(matches.get_one::<OsString>("data-dir").unwrap(), "/foo/bar");
}
#[actix_rt::test]

View file

@ -1,15 +1,13 @@
[package]
name = "taskchampion-sync-server-storage-sqlite"
version = "0.6.2-pre"
version = "0.5.0-pre"
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
edition = "2021"
description = "SQLite backend for TaskChampion-sync-server"
homepage = "https://github.com/GothenburgBitFactory/taskchampion"
repository = "https://github.com/GothenburgBitFactory/taskchampion-sync-server"
license = "MIT"
[dependencies]
taskchampion-sync-server-core = { path = "../core", version = "0.6.2-pre" }
taskchampion-sync-server-core = { path = "../core", version = "0.5.0-pre" }
uuid.workspace = true
anyhow.workspace = true
thiserror.workspace = true

View file

@ -385,23 +385,6 @@ mod test {
Ok(())
}
#[test]
fn test_add_version_exists() -> anyhow::Result<()> {
let tmp_dir = TempDir::new()?;
let storage = SqliteStorage::new(tmp_dir.path())?;
let client_id = Uuid::new_v4();
let mut txn = storage.txn(client_id)?;
let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let history_segment = b"abc".to_vec();
txn.add_version(version_id, parent_version_id, history_segment.clone())?;
assert!(txn
.add_version(version_id, parent_version_id, history_segment.clone())
.is_err());
Ok(())
}
#[test]
fn test_snapshots() -> anyhow::Result<()> {
let tmp_dir = TempDir::new()?;