mirror of
https://git.dirksys.ovh/dirk/bankserver.git
synced 2025-12-20 02:59:20 +01:00
initial testing infrastructure
This commit is contained in:
parent
02e1ffca4d
commit
456c507c10
@ -20,9 +20,13 @@ jobs:
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
- name: Build
|
||||
run: nix-fast-build --skip-cached --no-nom --attic-cache $ATTIC_CACHE --flake .#packages
|
||||
- name: Process results
|
||||
- name: Process build artifacts
|
||||
id: process-artifacts
|
||||
run: |
|
||||
mkdir images
|
||||
MANIFEST_TAG="git.dirksys.ovh/dirk/bankserver:latest"
|
||||
manifest_id=$(podman manifest create $MANIFEST_TAG)
|
||||
|
||||
for file in result-*dockerImage*; do
|
||||
if [ ! -f "$file" ]; then
|
||||
continue
|
||||
@ -31,9 +35,21 @@ jobs:
|
||||
IFS=$'\x1F' read -r hostArch name crossArch <<< "$info"
|
||||
arch=${crossArch:-$hostArch}
|
||||
containerArch=$(arch=$arch nix eval --raw --impure -I nixpkgs=flake:nixpkgs --expr '(import <nixpkgs> { system = builtins.getEnv "arch";}).go.GOARCH')
|
||||
|
||||
echo "Processed image for $containerArch"
|
||||
mv $file images/image-$containerArch.tar.gz
|
||||
target=images/image-$containerArch.tar.gz
|
||||
mv $file $target
|
||||
|
||||
echo "Loading $target"
|
||||
podman image load < $target
|
||||
image_id=$(podman image ls --format '{{.ID}}' | head -n 2 | tail -1)
|
||||
podman image untag $image_id
|
||||
echo "Adding $containerArch image to manifest"
|
||||
podman manifest add $manifest_id $image_id
|
||||
done
|
||||
echo "manifest-tag=$MANIFEST_TAG" >> "$GITHUB_OUTPUT"
|
||||
- name: Run tests
|
||||
run: just test
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@ -46,26 +62,14 @@ jobs:
|
||||
env:
|
||||
USERNAME: ${{ github.actor }}
|
||||
PASSWORD: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
- name: Push docker images
|
||||
- name: Push container images
|
||||
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'
|
||||
shell: bash
|
||||
env:
|
||||
MANIFEST_TAG: ${{ steps.process-artifacts.outputs.manifest-tag }}
|
||||
run: |
|
||||
manifest_id=$(podman manifest create git.dirksys.ovh/dirk/bankserver:latest)
|
||||
sleep 1
|
||||
for file in $(ls images); do
|
||||
echo "Loading images/$file"
|
||||
podman image load < images/$file
|
||||
image_id=$(podman image ls --format '{{.ID}}' | head -n 2 | tail -1)
|
||||
architecture=$(podman image inspect $image_id | jq -r ".[0].Architecture")
|
||||
tag=$(podman image ls --format "{{.Tag}}" | head -n 2 | tail -1)
|
||||
tag="git.dirksys.ovh/dirk/bankserver:$tag-$architecture"
|
||||
podman image untag $image_id
|
||||
echo "Adding $architecture image to manifest"
|
||||
podman manifest add $manifest_id $image_id
|
||||
done
|
||||
echo "Pushing manifest"
|
||||
podman login --get-login git.dirksys.ovh
|
||||
podman manifest push git.dirksys.ovh/dirk/bankserver:latest
|
||||
podman manifest push $MANIFEST_TAG
|
||||
- name: Notify server
|
||||
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@
|
||||
/result
|
||||
/schemas
|
||||
openapi.json
|
||||
|
||||
/tests/report
|
||||
|
||||
12
flake.nix
12
flake.nix
@ -126,10 +126,18 @@
|
||||
name = "bankserver";
|
||||
# tag = "latest";
|
||||
created = "now";
|
||||
contents = [ bankserver ];
|
||||
contents = [
|
||||
bankserver
|
||||
pkgs.catatonit
|
||||
];
|
||||
config = {
|
||||
Env = [ "HOST=::" ];
|
||||
Entrypoint = [ "/bin/bankserver" ];
|
||||
# Entrypoint = ["/bin/bankserver"];
|
||||
Entrypoint = [
|
||||
"/bin/catatonit"
|
||||
"--"
|
||||
];
|
||||
Cmd = [ "/bin/bankserver" ];
|
||||
ExposedPorts = {
|
||||
"3845/tcp" = { };
|
||||
};
|
||||
|
||||
8
justfile
8
justfile
@ -10,3 +10,11 @@ openapi:
|
||||
yq eval-all -n 'load("openapi-def.yaml") *n load("schemas/schemas.json")' > openapi-temp.yaml
|
||||
redocly bundle openapi-temp.yaml -o openapi.json
|
||||
rm openapi-temp.yaml
|
||||
|
||||
schemas:
|
||||
touch openapi.json
|
||||
cargo run --bin generate-schemas
|
||||
|
||||
test:
|
||||
tree .
|
||||
tests/ci.sh
|
||||
|
||||
@ -322,7 +322,7 @@ pub type State = axum::extract::State<Arc<AppState>>;
|
||||
pub fn router() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.merge(auth::router())
|
||||
.nest("/user", user::router())
|
||||
.nest("/users", user::router())
|
||||
.nest("/docs", docs::router())
|
||||
.nest("/accounts", account::router())
|
||||
.nest("/transactions", transactions::router())
|
||||
|
||||
@ -23,8 +23,7 @@ pub(super) fn router() -> Router<Arc<AppState>> {
|
||||
.route("/", get(list_users))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum UserTarget {
|
||||
#[serde(rename = "@me")]
|
||||
Me,
|
||||
@ -104,3 +103,18 @@ pub async fn user_accounts(EState(state): State, auth: Auth) -> Result<Json<User
|
||||
let result = Account::list_for_user(&conn, user).await?;
|
||||
Ok(Json(UserAccounts { accounts: result }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::UserTarget;
|
||||
|
||||
#[test]
|
||||
fn user_target() {
|
||||
let json = serde_json::to_string(&UserTarget::Me).unwrap();
|
||||
assert_eq!(json, r#""@me""#);
|
||||
let json = serde_json::to_string(&Uuid::nil()).unwrap();
|
||||
assert_eq!(json, r#""00000000-0000-0000-0000-000000000000""#);
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,10 +75,15 @@ impl Account {
|
||||
name: &str,
|
||||
) -> Result<Uuid, tokio_postgres::Error> {
|
||||
let stmt = client
|
||||
.prepare_cached("insert into accounts(id, \"user\", name) values ($1, $2, $3)")
|
||||
.prepare_cached(
|
||||
"insert into accounts(id, \"user\", name, balance) values ($1, $2, $3, $4)",
|
||||
)
|
||||
.await?;
|
||||
let id = id.unwrap_or_else(|| Uuid::new_v7(Timestamp::now(NoContext)));
|
||||
client.execute(&stmt, &[&id, &user, &name]).await?;
|
||||
let balance: i64 = if id == user { 1000 * 100 } else { 0 };
|
||||
client
|
||||
.execute(&stmt, &[&id, &user, &name, &balance])
|
||||
.await?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
|
||||
@ -50,12 +50,13 @@ impl User {
|
||||
name: &str,
|
||||
password: &str,
|
||||
) -> Result<Uuid, Error> {
|
||||
let hash = password_auth::generate_hash(password);
|
||||
let stmt = client
|
||||
.prepare_cached("insert into users(id, name, password) values ($1, $2, $3)")
|
||||
.await?;
|
||||
let mut tx = client.transaction().await?;
|
||||
let id = Uuid::new_v7(Timestamp::now(NoContext));
|
||||
tx.execute(&stmt, &[&id, &name, &password])
|
||||
tx.execute(&stmt, &[&id, &name, &hash])
|
||||
.await
|
||||
.map_err(|err| unique_violation(err, conflict_error))?;
|
||||
Account::create(&mut tx, Some(id), id, name)
|
||||
@ -125,7 +126,7 @@ impl User {
|
||||
let stmt = client
|
||||
.prepare_cached("select id,name from users where id = $1")
|
||||
.await?;
|
||||
let info = client.query_opt(&stmt, &[]).await?.map(User::from);
|
||||
let info = client.query_opt(&stmt, &[&id]).await?.map(User::from);
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
|
||||
85
tests/ci.sh
Executable file
85
tests/ci.sh
Executable file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
ENV_FILE=".env"
|
||||
RUN_DIR="/run/postgresql"
|
||||
|
||||
if [ ! -z "${CI:-}" ]; then
|
||||
echo "Running in CI"
|
||||
fi
|
||||
|
||||
if ss -tulwn | grep ":5432" -q; then
|
||||
echo "Postgres running"
|
||||
else
|
||||
echo "Postgres not running"
|
||||
|
||||
if [ ! -z "${CI:-}" ]; then
|
||||
# Create a temporary directory and store its name in a variable.
|
||||
TEMPD=$(mktemp -d)
|
||||
|
||||
# Exit if the temp directory wasn't created successfully.
|
||||
if [ ! -e "$TEMPD" ]; then
|
||||
>&2 echo "Failed to create temp directory"
|
||||
exit 1
|
||||
fi
|
||||
mkdir "$TEMPD"/run
|
||||
ENV_FILE="$TEMPD/.env"
|
||||
echo -e "DATABASE__USER=bankserver\nDATABASE__DBNAME=bankserver\nJWT_KEY=ABCDEFG" > $ENV_FILE
|
||||
|
||||
DB_RUNNING=1
|
||||
RUN_DIR="$TEMPD"/run
|
||||
podman run --name bankserver-db --rm --replace --detach -v "$RUN_DIR":/run/postgresql -e POSTGRES_USER=bankserver -e POSTGRES_HOST_AUTH_METHOD=trust postgres
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
|
||||
function cleanup {
|
||||
if [ ! -z "${APP_RUNNING:-}" ]; then
|
||||
if [ ! -z "${CI:-}" ]; then
|
||||
echo "Stopping app instance"
|
||||
podman stop bankserver -t 2
|
||||
else
|
||||
pkill -P $JOB_PID
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -z "${DB_RUNNING:-}" ]; then
|
||||
echo "Stopping database"
|
||||
podman stop bankserver-db -t 2
|
||||
fi
|
||||
|
||||
if [ ! -z "${TEMPD:-}" ]; then
|
||||
rm -rf "$TEMPD"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
HOST="http://[::1]:3845"
|
||||
|
||||
wait_for_url () {
|
||||
echo "Testing $1..."
|
||||
printf 'GET %s\nHTTP 404' "$1" | hurl --retry "$2";
|
||||
return 0
|
||||
}
|
||||
|
||||
echo "Starting application"
|
||||
|
||||
APP_RUNNING="1"
|
||||
if [ ! -z "${CI:-}" ]; then
|
||||
echo $RUN_DIR
|
||||
ls -la $RUN_DIR
|
||||
podman run --name bankserver --rm --replace --detach --env-file $ENV_FILE --network host -v "$RUN_DIR":/run/postgresql --publish 3845:3845 --pull never bankserver:latest
|
||||
else
|
||||
just dev &
|
||||
JOB_PID=$!
|
||||
fi
|
||||
|
||||
echo "Waiting for app instance to be ready"
|
||||
wait_for_url "$HOST" 20
|
||||
|
||||
echo "Creating dummy users"
|
||||
hurl --variable host="$HOST" --error-format long tests/dummy-users.hurl
|
||||
|
||||
echo "Running tests"
|
||||
hurl --variable host="$HOST" --test --error-format long --color --report-html tests/report tests/integration/*.hurl || true
|
||||
36
tests/dummy-users.hurl
Normal file
36
tests/dummy-users.hurl
Normal file
@ -0,0 +1,36 @@
|
||||
POST {{host}}/api/register
|
||||
{
|
||||
"name": "user1",
|
||||
"password": "this-is-a-password"
|
||||
}
|
||||
HTTP 201
|
||||
POST {{host}}/api/register
|
||||
{
|
||||
"name": "user2",
|
||||
"password": "this-is-a-password"
|
||||
}
|
||||
HTTP 201
|
||||
POST {{host}}/api/register
|
||||
{
|
||||
"name": "user3",
|
||||
"password": "this-is-a-password"
|
||||
}
|
||||
HTTP 201
|
||||
POST {{host}}/api/register
|
||||
{
|
||||
"name": "user4",
|
||||
"password": "this-is-a-password"
|
||||
}
|
||||
HTTP 201
|
||||
POST {{host}}/api/register
|
||||
{
|
||||
"name": "user5",
|
||||
"password": "this-is-a-password"
|
||||
}
|
||||
HTTP 201
|
||||
POST {{host}}/api/register
|
||||
{
|
||||
"name": "user6",
|
||||
"password": "this-is-a-password"
|
||||
}
|
||||
HTTP 201
|
||||
20
tests/integration/register.hurl
Normal file
20
tests/integration/register.hurl
Normal file
@ -0,0 +1,20 @@
|
||||
POST {{host}}/api/register
|
||||
{
|
||||
"name": "test-user",
|
||||
"password": "this-is-a-test"
|
||||
}
|
||||
HTTP 201
|
||||
|
||||
[Captures]
|
||||
token: jsonpath "$.token"
|
||||
|
||||
GET {{host}}/api/users/@me
|
||||
Authorization: Bearer {{token}}
|
||||
HTTP 200
|
||||
|
||||
POST {{host}}/api/login
|
||||
{
|
||||
"name": "test-user",
|
||||
"password": "this-is-a-test"
|
||||
}
|
||||
HTTP 200
|
||||
2
tests/reset.sh
Executable file
2
tests/reset.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#/bin/sh
|
||||
psql -tac "drop schema public cascade; create schema public; grant all on schema public to $DATABASE__USER;" -d $DATABASE__DBNAME
|
||||
Loading…
x
Reference in New Issue
Block a user