auto_clea

This commit is contained in:
LeCoffre Deployment 2025-09-25 15:03:44 +00:00
parent 08b1383109
commit d94a8c430e
25 changed files with 580 additions and 9 deletions

View File

@ -0,0 +1,33 @@
### Lancement des services (séquencé avec attentes)
- Préparer les scripts dattente (une fois):
```bash
chmod +x /home/debian/4NK_env/scripts/lecoffre_node/wait-*.sh
```
- Démarrer linfrastructure (dans `lecoffre_node/`):
```bash
cd /home/debian/4NK_env/lecoffre_node
# Phase 1: Base + Bitcoin
docker compose up -d tor bitcoin
/home/debian/4NK_env/scripts/lecoffre_node/wait-tor-bootstrap.sh 120 5
/home/debian/4NK_env/scripts/lecoffre_node/wait-bitcoin-ready.sh 180 5
# Phase 2: BlindBit
docker compose up -d blindbit
/home/debian/4NK_env/scripts/lecoffre_node/wait-blindbit-ready.sh 180 5
# Phase 3: Services applicatifs
docker compose up -d sdk_storage ihm_client lecoffre-front
# Phase 4: Monitoring
docker compose up -d loki promtail grafana status-api watchtower
```
- Vérifications rapides:
```bash
curl -sS -D - https://dev4.4nkweb.com/lecoffre/ -o /dev/null | sed -n '1,20p'
curl -sS -D - http://127.0.0.1:3003/ -o /dev/null | sed -n '1,20p'
```

View File

@ -21,6 +21,7 @@ Tâches attendues, toutes obligatoires, pour tous les dossiers hors `4NK_env/dat
* [ ] Vérifier et améliorer la conformité avec `4NK_env/IA_agents/prompts/prompt-scripts.md`
* [ ] Vérifier et améliorer la conformité avec `4NK_env/IA_agents/prompts/prompt-tests.md`
* [ ] Vérifier et améliorer la conformité avec `4NK_env/IA_agents/prompts/prompt-docs.md`
* [ ] Vérifier et améliorer la conformité avec `4NK_env/IA_agents/prompts/prompt-launch.md`
* [ ] Analyser le contexte technique et préciser les prérequis : projets, CI, variables d'environnement, documentation, code, configuration, logs, backup, synergie avec `4NK_env/lecoffre_node/` pour comprendre ce que cela fait.
* [ ] Mettre à jour la documentation de l'architecture `4NK_env/docs/architecture/<projet>.md`.

View File

@ -4,7 +4,7 @@ server {
http2 on;
server_name dev4.4nkweb.com;
include /home/debian/4NK_env/lecoffre_node/conf/nginx/logging.conf;
include /home/debian/4NK_env/confs/lecoffre_node/nginx/logging.conf;
# Certificats SSL
ssl_certificate /etc/letsencrypt/live/dev4.4nkweb.com/fullchain.pem;
@ -192,7 +192,7 @@ server {
# favicon
location = /favicon.ico {
root /home/debian/4NK_env/lecoffre_node/conf/nginx/assets;
root /home/debian/4NK_env/confs/lecoffre_node/nginx/assets;
try_files /favicon.ico =404;
}

View File

@ -216,7 +216,7 @@ server {
# favicon
location = /favicon.ico {
root /home/debian/4NK_env/lecoffre_node/conf/nginx/assets;
root /home/debian/4NK_env/confs/lecoffre_node/nginx/assets;
try_files /favicon.ico =404;
access_log off;
expires 30d;

View File

@ -185,7 +185,7 @@ server {
# favicon
location = /favicon.ico {
root /home/debian/4NK_env/lecoffre_node/conf/nginx/assets;
root /home/debian/4NK_env/confs/lecoffre_node/nginx/assets;
try_files /favicon.ico =404;
}

@ -1 +1 @@
Subproject commit 390015e82366bf2ffb23cbd32063e1849bf5d1d1
Subproject commit 1b5f672f62aad7232606a7c15b9c796617b5bdc9

@ -1 +1 @@
Subproject commit c130e2d588409ffb9cd242d0212efb51cba467a0
Subproject commit 2a62c2649d10d33be58c1c644e70cac156dffc48

@ -1 +1 @@
Subproject commit b876f9375379743c1a0d6fe1487b33d68a1a6482
Subproject commit 912fe1c96dc9b2032f95405a5a137e3e552fe419

View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR/lecoffre_node"
max_tries=${1:-120}
sleep_secs=${2:-5}
for i in $(seq 1 "$max_tries"); do
height="unknown"
if out=$(docker compose exec -T bitcoin sh -lc 'bitcoin-cli -signet getblockcount' 2>/dev/null); then
height="$out"
fi
if docker compose exec -T bitcoin sh -lc 'bitcoin-cli -signet -rpccookiefile=/home/bitcoin/.bitcoin/signet/.cookie getblockchaininfo >/dev/null' >/dev/null 2>&1; then
echo "Bitcoin ready at block $height (try $i)"
exit 0
fi
echo "Wait sync (bitcoin), now: $height (try $i)"
sleep "$sleep_secs"
done
echo "Timeout waiting for Bitcoin"
exit 1

View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR/lecoffre_node"
max_tries=${1:-60}
sleep_secs=${2:-5}
for i in $(seq 1 "$max_tries"); do
# Get current scanned height from blindbit logs or api
scanned="unknown"
if out=$(docker compose logs --tail=200 blindbit | grep -oE 'successfully processed block.*height=\s*[0-9]+' | tail -n1 | grep -oE '[0-9]+$'); then
scanned="$out"
fi
if docker compose exec -T blindbit sh -lc 'wget -q --spider http://127.0.0.1:8000/tweaks/1' >/dev/null 2>&1; then
echo "BlindBit ready at scanned block ${scanned} (try $i)"
exit 0
fi
echo "Wait sync (blindbit), now scanned: ${scanned} (try $i)"
sleep "$sleep_secs"
done
echo "Timeout waiting for BlindBit readiness"
exit 1

View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR/lecoffre_node"
max_tries=${1:-120}
sleep_secs=${2:-5}
for i in $(seq 1 "$max_tries"); do
pct="unknown"
if out=$(docker compose logs --tail=50 tor | grep -oE 'Bootstrapped [0-9]+%' | tail -n1 | cut -d' ' -f2); then
pct="$out"
fi
if [ "$pct" = "100%" ]; then
echo "Tor bootstrap ready (100%) (try $i)"
exit 0
fi
echo "Wait bootstrap (tor), now: ${pct:-unknown} (try $i)"
sleep "$sleep_secs"
done
echo "Timeout waiting for Tor bootstrap"
exit 1

@ -1 +1 @@
Subproject commit c00a17a5c605ead2f8be158c95a57cbc7d832959
Subproject commit a20dbd2f9f0b51ea08529a3ab4b220b4a89b1535

@ -1 +1 @@
Subproject commit 8fd81ea3aa48a49f4b24c56cc64dc8e53764efb4
Subproject commit bc9bd7b92a3da2a6efa3454997375dd611ca5dba

View File

@ -0,0 +1,16 @@
### Objet
Axes de tests pour `ihm_client` (sans exemples).
### Couverture prioritaire
- **Chargement iframe**: initialisation, messages parent ↔ iframe
- **Auth/Token**: création, stockage, renouvellement, invalidation
- **Pages**: home, chat, account, process, signature (navigation, états)
- **WebSockets**: connexion, reconnexion, messages, erreurs
- **Modales**: confirmation/creation/waiting/validation-rule
### Performance
- **Workers**: cache et base de données (latences, tailles)
### Sécurité
- **postMessage**: validation dorigine, format messages
- **Stockage**: isolation domain/cookies/localStorage

View File

@ -0,0 +1,27 @@
### Objet
Axes de tests pour `lecoffre-front` (sans exemples dimplémentation).
### Couverture prioritaire
- **Routage**: accessibilité des pages clés sous `basePath` `/lecoffre`
- **Auth**: parcours login client et callbacks (Id360/IdNot)
- **Tableau client**: chargement données, états vides, erreurs
- **Dossiers/Documents**: création/affichage, téléchargements, filigrane
- **Souscription**: parcours complet (erreur/succès/gestion)
- **Notifications/Toasts**: affichage cohérent des erreurs
### Données et intégrations
- **API Back**: validation des URL via `NEXT_PUBLIC_BACK_API_*` et `NEXT_PUBLIC_API_URL`
- **IdNot Auth**: vérifier que lappel se fait en POST `/api/v1/idnot/auth` avec `{ code }` dans le corps, et quaucune URL longue nest utilisée.
- **Idnot/Docaposte**: vérification des redirections et scopes
### Non-régressions UI/UX
- **DesignSystem**: composants critiques (boutons, tabs, formulaires)
- **Accessibilité**: focus, contrastes, navigation clavier
### Performance
- **Chargement initial**: taille bundle avec `React.lazy`/`Suspense` si applicable
- **Rendu**: éviter re-renders via stores et mémoïsations locales
### Sécurité
- **Données sensibles**: absence de secrets dans `NEXT_PUBLIC_*`
- **JWT**: décodage côté client limité aux besoins

View File

@ -0,0 +1,32 @@
### Plan de tests CI/CD
Ce document liste les scénarios de test pour valider la chaîne CI/CD décrite dans `docs/ci.md`.
### Pré-requis
- Accès au registre Scaleway avec droits de push/pull.
- Accès au cluster Kubernetes `lecoffre` et à Vault (lecture des chemins référencés).
- BuildKit activé et agent SSH opérationnel pour laccès `git.4nkweb.com`.
### Tests de build
- Vérifier linstallation des dépendances avec accès SSH aux ressources privées.
- Exécuter `npm run build` et confirmer la génération sans erreurs.
### Tests dimage Docker
- Construire limage avec le forward SSH.
- Valider la taille, les couches, lutilisateur non-root, et lexécution `npm run start`.
- Pousser limage taguée (ex. `ext`) vers le registry `git.4nkweb.com` et vérifier la présence.
### Tests Kubernetes
- Appliquer les manifests/Helm sur un environnement de test.
- Valider la création de l`ExternalSecret` et du `imagePullSecret`.
- Vérifier que le `Deployment` démarre, que Vault injecte les variables, et que le `Service` et `Ingress` sont fonctionnels.
- Vérifier que `NEXT_PUBLIC_4NK_URL` (origin) et `NEXT_PUBLIC_4NK_IFRAME_URL` (URL complète) sont bien injectées au build (présentes dans `/lecoffre-front/.next/standalone/.env` ou via `next.config.js`) et cohérentes avec liframe réellement servie.
### Observabilité et rollback
- Vérifier les logs dinjection Vault et de lapplication.
- Tester un rollback dimage (retag vers version précédente) et vérifier la restauration.

View File

@ -0,0 +1,28 @@
### Tests image "ext"
Objectif: vérifier que l'image démarre et que les URLs d'API proviennent des variables d'environnement.
Plan de test manuel:
1. Vérifier l'injection des variables NEXT_PUBLIC_*
```
docker pull git.4nkweb.com/4nk/lecoffre-front:ext
docker run --rm git.4nkweb.com/4nk/lecoffre-front:ext sh -lc "env | grep '^NEXT_PUBLIC_' | sort"
```
Attendus (exemples):
- `NEXT_PUBLIC_4NK_URL`, `NEXT_PUBLIC_4NK_IFRAME_URL` définies
- `NEXT_PUBLIC_API_URL` et `NEXT_PUBLIC_BACK_API_*` cohérentes
2. Vérifier que l'app démarre
```
docker run --rm -p 3001:3000 git.4nkweb.com/4nk/lecoffre-front:ext
# Ouvrir http://localhost:3001/lecoffre
```
Critères de réussite:
- Le conteneur écoute sur 3000 et répond.
- Les URLs d'API proviennent des variables d'environnement.

View File

@ -0,0 +1,27 @@
## Axes de tests — lecoffre_node
### Pré-requis
- Nginx rechargé et conf active (`dev4.4nkweb.com.conf`)
- Réseau Docker `4nk_node_btcnet` présent
- Volumes montés (`4nk_node_bitcoin_data`, `blindbit_data`, `sdk_data`)
### Démarrage et dépendances
- Ordre de démarrage observé vs recommandé (voir docs/analyse.md)
- Healthchecks: `bitcoin` (CLI), `blindbit` (HTTP), `sdk_relay` (`/health`)
### Routage Nginx
- `/back/*` et `/api/*` → backend 8080 (statuts 200, pas de HTML)
- `/lecoffre/` et `/_next/` → front 3004 avec basePath OK
- `/signer/` WebSocket (101), `/blindbit/` (200)
### CORS
- Origines autorisées: `http://dev3.4nkweb.com`, `https://dev4.4nkweb.com`
- Prévols `OPTIONS` (204) et en-têtes `Access-Control-*`
### Non-régression
- Absence de `localhost:8080` dans les bundles front servis via `/lecoffre/`
- Redirections locales `dev3.4nkweb.com``https://dev4.4nkweb.com/lecoffre/`
### Observabilité
- Journaux Nginx (`error.log`) sans erreurs après reload
- Logs `sdk_relay` présents dans `/home/bitcoin/.4nk/logs/sdk_relay.log`

View File

@ -0,0 +1,71 @@
### Tests de routage API et front (dev4)
Pré-requis: Nginx rechargé et services Docker démarrés.
1) Santé backend via Nginx (HTTP)
Commande:
curl -sS -D - http://127.0.0.1/back/v1/health -o /dev/null | sed -n '1,10p'
Attendu: `HTTP/1.1 200` (ou `302` selon auth), jamais du HTML de front.
2) Santé backend via Nginx (HTTPS)
Commande:
curl -sS -D - https://dev4.4nkweb.com/back/v1/health -o /dev/null | sed -n '1,10p'
Attendu: `HTTP/2 200`.
3) Front `/lecoffre` (HTTPS)
Commande:
curl -sS -D - https://dev4.4nkweb.com/lecoffre/ -o /dev/null | sed -n '1,10p'
Attendu: `HTTP/2 200` et pas de 301/302 en boucle.
### Plan de tests — routage API via Nginx
#### Pré-requis
- Nginx actif avec `conf/nginx/dev4.4nkweb.com.conf` chargé.
- Services: backend (8080), storage (8081), ws relay (8090), blindbit (8000), front (3000/3003).
#### Vérifications HTTP
- GET `https://dev4.4nkweb.com/back/health` → 200
- (Si alias validé) GET `https://dev4.4nkweb.com/api/health` → 200
- GET `https://dev4.4nkweb.com/storage/health` → 200
#### Vérifications front
- Ouvrir `https://dev4.4nkweb.com/lecoffre/` → 200, pas derreurs CORS ni `ERR_BLOCKED_BY_CLIENT`.
- Ouvrir la console réseau: les appels API doivent cibler `/back/...` (ou `/api/...`) sous le même hôte.
#### Non-régression: absence de `localhost:8080` dans les bundles
- Télécharger la page et scanner les scripts:
- `curl -sS https://dev4.4nkweb.com/lecoffre/ -o /tmp/lecoffre_index.html`
- `grep -oP '(?<=<script src=")[^"]+' /tmp/lecoffre_index.html | sed 's#^/#https://dev4.4nkweb.com/#' | while read u; do curl -sS "$u" | grep -q 'localhost:8080' && echo "KO: $u"; done`
- Attendu: aucun `KO:`
#### Vérification réécriture `sub_filter`
- Contrôler quaucune requête ne vise `http://localhost:8080`:
- Recharger avec cache vidé et surveiller longlet Réseau.
- Télécharger une ressource HTML/JS et vérifier labsence de `http://localhost:8080`.
#### Redirections locales
- `curl -I http://dev3.4nkweb.com/` → 301 Location `https://dev4.4nkweb.com/lecoffre/`
- `curl -I http://dev3.4nkweb.com/authorized-client?code=ABC` → 301 Location `https://dev4.4nkweb.com/lecoffre/authorized-client?code=ABC`
#### WebSocket
- `wss://dev4.4nkweb.com/ws` handshake OK (101)
#### Journaux Nginx
- `error.log`: aucun message critique après rechargement.

View File

@ -0,0 +1,18 @@
# Test manuel: import des descripteurs Signet
## Préparation
- Vérifier `miner/.env.signet` (tprv, fingerprint, path)
## Étapes
1. `cd miner && ./tools/import_signet_descriptors.sh`
2. Vérifier sortie:
- Deux entrées `success: true` (external/internal)
- Une entrée `success: true` pour le challenge (warnings admis)
- `COINBASE_ADDRESS=tb1...` affichée
3. `docker compose up -d --build signet_miner`
4. `docker logs --tail=200 signet_miner`
- Attendre `Mined block at height ...`
## Échecs courants
- `Cannot import descriptor without private keys...` => utiliser descripteurs xprv+#checksum, pas la version normalisée (tpub).
- Wallet non-descriptor => recréer via le script (unload + rm -rf + createwallet descriptors=true).

View File

@ -0,0 +1,27 @@
# Tests de fumée dev4
HTTP/HTTPS
- GET / (ihm_client) attend 200 → Observé: 200
- GET /lecoffre/ attend 200 → Observé: 404 (page 404 servie, app OK sous /lecoffre/404)
- GET /back/ attend 200 → Observé: 200
- GET /storage/ attend 200 → Observé: 404 (à valider: endpoint /storage/health?)
WebSocket
- wss://dev4.4nkweb.com/ws handshake OK → À vérifier (outil ws requis côté serveur)
Redirections
- GET http://dev3.4nkweb.com/ attend 301 → Location: https://dev4.4nkweb.com/lecoffre/
- GET http://dev3.4nkweb.com/authorized-client?code=... attend 301 → Location: https://dev4.4nkweb.com/lecoffre/authorized-client?code=...
DNS côté client
- dev3.4nkweb.com doit résoudre vers 92.243.24.12 (pas 127.0.0.1)
- Test: nslookup dev3.4nkweb.com sur machine cliente
Nginx
- Certificats valides
- Pas derreurs critiques dans error.log
Résultats (17/09/2025)
- /lecoffre/ → 404
- /lecoffre/404 → 404 (avec `runtimeConfig` présent et `assetPrefix` = /lecoffre)
- /back/ → 200
- /blindbit/ → 200
- /storage/ → 404

View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[TEST] sdk_relay: vérification santé"
health=$(curl -sS http://127.0.0.1:8091/health || true)
echo "Health: ${health}"
if [[ "${health}" != *'"status":"ok"'* ]]; then
echo "[ERREUR] Healthcheck non OK" >&2
exit 1
fi
echo "[TEST] sdk_relay: variables d'environnement effectives"
envs=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' sdk_relay | sort)
echo "ENV:\n${envs}"
echo "Vérif HOME=/home/bitcoin"
grep -q '^HOME=/home/bitcoin$' <<<"${envs}"
echo "Vérif RUST_LOG=DEBUG"
grep -q '^RUST_LOG=DEBUG$' <<<"${envs}"
echo "Vérif NODE_OPTIONS --max-old-space-size=2048"
grep -q '^NODE_OPTIONS=--max-old-space-size=2048$' <<<"${envs}"
echo "[TEST] sdk_relay: logs récents (erreurs connues)"
logs=$(docker logs --since=10m sdk_relay 2>&1 || true)
echo "--- DERNIERS LOGS (tronqués) ---"
echo "${logs}" | tail -n 200
echo "--- FIN LOGS ---"
if echo "${logs}" | grep -qi 'failed to open bitcoind cookie file'; then
echo "[ERREUR] Problème d'accès au cookie RPC bitcoin détecté" >&2
exit 1
fi
echo "[SUCCES] Tests sdk_relay passés"

23
tests/sdk_relay/health_check.sh Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
URL="http://localhost:8091/health"
echo "[tests] Vérification /health: $URL"
http_code=$(curl -s -o /tmp/health_body.txt -w "%{http_code}" "$URL")
body=$(cat /tmp/health_body.txt)
echo "HTTP $http_code"
echo "Body: $body"
if [[ "$http_code" != "200" ]]; then
echo "Échec: code HTTP inattendu" >&2
exit 1
fi
if [[ "$body" != '{"status":"ok"}' ]]; then
echo "Échec: corps inattendu" >&2
exit 1
fi
echo "Succès: endpoint /health opérationnel"

View File

@ -0,0 +1,22 @@
use std::time::Duration;
use tokio::time::timeout;
use tokio_tungstenite::connect_async;
#[tokio::test(flavor = "multi_thread")]
async fn ws_connects_on_localhost_8090() {
if std::env::var("RUN_WS_TEST").ok().as_deref() != Some("1") {
// Test conditionnel: désactivé par défaut pour éviter les échecs en l'absence de serveur WS local
return;
}
let url = std::env::var("SDK_RELAY_WS_URL").unwrap_or_else(|_| "ws://0.0.0.0:8090".to_string());
let connect_fut = connect_async(url);
let res = timeout(Duration::from_secs(3), connect_fut).await;
match res {
Ok(Ok((_stream, _resp))) => {
// Succès si la poignée de main WS passe
}
Ok(Err(e)) => panic!("Échec connexion WebSocket: {e}"),
Err(_) => panic!("Timeout connexion WebSocket"),
}
}

View File

@ -0,0 +1,135 @@
use sdk_storage::{StorageService, unix_to_system_time, create_app};
use tempfile::TempDir;
use surf::Client;
#[async_std::test]
async fn store_and_retrieve_hex_in_tempdir() {
let td = TempDir::new().unwrap();
let dir_path = td.path().to_string_lossy().to_string();
let svc = StorageService::new(dir_path);
let key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let value = b"hello";
let expires = Some(unix_to_system_time(60 + sdk_storage::system_time_to_unix(std::time::SystemTime::now())));
svc.store_data(key, value, expires).await.unwrap();
let got = svc.retrieve_data(key).await.unwrap();
assert_eq!(got, value);
}
#[async_std::test]
async fn conflict_on_duplicate_key() {
let td = TempDir::new().unwrap();
let dir_path = td.path().to_string_lossy().to_string();
let svc = StorageService::new(dir_path);
let key = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
let value = b"data";
svc.store_data(key, value, None).await.unwrap();
let err = svc.store_data(key, value, None).await.err().expect("should error");
assert_eq!(err.status(), tide::StatusCode::Conflict);
}
#[async_std::test]
async fn cleanup_removes_expired() {
let td = TempDir::new().unwrap();
let dir_path = td.path().to_string_lossy().to_string();
let svc = StorageService::new(dir_path.clone());
let key = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
let value = b"x";
// expiration très proche: maintenant - 1s
let past = sdk_storage::system_time_to_unix(std::time::SystemTime::now()).saturating_sub(1);
let expires = Some(unix_to_system_time(past));
svc.store_data(key, value, expires).await.unwrap();
// cleanup one-shot
svc.cleanup_expired_files_once().await.unwrap();
let res = svc.retrieve_data(key).await;
assert!(res.is_err(), "expired key should be removed");
}
#[async_std::test]
async fn http_store_success_and_conflicts_and_invalids() {
// app with permanent=false so default TTL applies when missing
let td = TempDir::new().unwrap();
let storage = td.path().to_string_lossy().to_string();
let mut app = create_app(false, storage);
let listener = async_std::net::TcpListener::bind("0.0.0.0:0").await.unwrap();
let addr = listener.local_addr().unwrap();
async_std::task::spawn(async move { app.listen(listener).await.unwrap() });
let client = Client::new();
let base = format!("http://{}", addr);
// success
let key = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
let body = serde_json::json!({"key": key, "value": "01aa", "ttl": 120});
let res = client.post(format!("{}/store", base)).body_json(&body).unwrap().await.unwrap();
assert!(res.status().is_success());
// conflict
let res2 = client.post(format!("{}/store", base)).body_json(&body).unwrap().await.unwrap();
assert_eq!(res2.status(), 409);
// invalid key
let bad = serde_json::json!({"key": "xyz", "value": "01"});
let res3 = client.post(format!("{}/store", base)).body_json(&bad).unwrap().await.unwrap();
assert_eq!(res3.status(), 400);
// invalid value
let badv = serde_json::json!({"key": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "value": "zz"});
let res4 = client.post(format!("{}/store", base)).body_json(&badv).unwrap().await.unwrap();
assert_eq!(res4.status(), 400);
}
#[async_std::test]
async fn http_retrieve_success_and_invalid_and_notfound() {
let td = TempDir::new().unwrap();
let storage = td.path().to_string_lossy().to_string();
let mut app = create_app(true, storage.clone());
let listener = async_std::net::TcpListener::bind("0.0.0.0:0").await.unwrap();
let addr = listener.local_addr().unwrap();
async_std::task::spawn(async move { app.listen(listener).await.unwrap() });
let client = Client::new();
let base = format!("http://{}", addr);
// prepare stored value (permanent mode)
let svc = StorageService::new(storage);
let key = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
svc.store_data(key, b"hi", None).await.unwrap();
// success
let mut res = client.get(format!("{}/retrieve/{}", base, key)).await.unwrap();
assert!(res.status().is_success());
let v: serde_json::Value = res.body_json().await.unwrap();
assert_eq!(v["value"].as_str().unwrap(), hex::encode("hi"));
// invalid key
let res2 = client.get(format!("{}/retrieve/{}", base, "bad")).await.unwrap();
assert_eq!(res2.status(), 400);
// not found
let k2 = "1111111111111111111111111111111111111111111111111111111111111111";
let res3 = client.get(format!("{}/retrieve/{}", base, k2)).await.unwrap();
assert_eq!(res3.status(), 404);
}
#[async_std::test]
async fn http_health_ok() {
let td = TempDir::new().unwrap();
let storage = td.path().to_string_lossy().to_string();
let mut app = create_app(true, storage);
let listener = async_std::net::TcpListener::bind("0.0.0.0:0").await.unwrap();
let addr = listener.local_addr().unwrap();
async_std::task::spawn(async move { app.listen(listener).await.unwrap() });
let client = Client::new();
let base = format!("http://{}", addr);
let mut res = client.get(format!("{}/health", base)).await.unwrap();
assert!(res.status().is_success());
let v: serde_json::Value = res.body_json().await.unwrap();
assert_eq!(v["message"].as_str().unwrap(), "ok");
}