CodeBench: Rust golden example — todo-rs.md + kielitietoinen valinta
- Luotu todo-rs.md golden example Rust-referenssitoteutuksesta
- getGoldenForModel() huomioi nyt LANG: todo.md → todo-rs.md Rust-moodissa
- Korjattu golden-compact-rs.md /:id → /{id} bugi
- Juurisyy: malli sai Python golden examplen mutta piti generoida Rustia
This commit is contained in:
@@ -53,6 +53,12 @@ const PROFILES = JSON.parse(readFileSync(join(__dirname, 'profiles.json'), 'utf-
|
|||||||
function getGoldenForModel(model) {
|
function getGoldenForModel(model) {
|
||||||
const modelConf = PROFILES.models[model];
|
const modelConf = PROFILES.models[model];
|
||||||
const goldenFile = modelConf?.golden || 'todo.md';
|
const goldenFile = modelConf?.golden || 'todo.md';
|
||||||
|
// Rust-kielelle vaihda .md → -rs.md (todo.md → todo-rs.md)
|
||||||
|
if (LANG === 'rust') {
|
||||||
|
const rsFile = goldenFile.replace(/\.md$/, '-rs.md');
|
||||||
|
const rsPath = join(GOLDEN_DIR, rsFile);
|
||||||
|
if (existsSync(rsPath)) return rsFile;
|
||||||
|
}
|
||||||
return goldenFile;
|
return goldenFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
540
kipina-codebench/golden-examples/todo-rs.md
Normal file
540
kipina-codebench/golden-examples/todo-rs.md
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
# Todo — referenssitoteutus (Axum 0.8 + SQLx + SQLite)
|
||||||
|
|
||||||
|
Tämä on täydellinen esimerkki. Generoi vastaava rakenne annetulle projektille.
|
||||||
|
Käytä VAIN JSON-spekin kenttiä — älä lisää ylimääräisiä.
|
||||||
|
|
||||||
|
## Cargo.toml
|
||||||
|
|
||||||
|
Axum 0.8, SQLx SQLite-featurella, serde JSON-serialisointiin, tower-http CORS-tukeen.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "todo-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.8"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio"] }
|
||||||
|
tower-http = { version = "0.6", features = ["cors"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
|
tokio = { version = "1", features = ["full", "test-util"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## src/models.rs
|
||||||
|
|
||||||
|
Serde-rakenteet: `Todo` (FromRow), `CreateTodo` (POST), `UpdateTodo` (PUT, kaikki kentät valinnaisia).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! Tietomallit — Todo, CreateTodo, UpdateTodo serde-rakenteina.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Tehtävä — otsikko, kuvaus, deadline, prioriteetti ja status.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
|
||||||
|
pub struct Todo {
|
||||||
|
pub id: i64,
|
||||||
|
pub title: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub due_date: Option<String>,
|
||||||
|
pub priority: i64,
|
||||||
|
pub status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uuden tehtävän luonti. Pakolliset: title.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct CreateTodo {
|
||||||
|
pub title: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub due_date: Option<String>,
|
||||||
|
pub priority: Option<i64>,
|
||||||
|
pub status: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tehtävän päivitys — kaikki kentät valinnaisia.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct UpdateTodo {
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub due_date: Option<String>,
|
||||||
|
pub priority: Option<i64>,
|
||||||
|
pub status: Option<String>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## src/handlers.rs
|
||||||
|
|
||||||
|
CRUD-käsittelijät: POST 201 RETURNING, GET lista, GET by id 404, PUT (merge olemassaolevaan), DELETE 204.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! Käsittelijät — CRUD-operaatiot todo-entiteetille.
|
||||||
|
|
||||||
|
use axum::extract::{Path, State};
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Json;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
|
use crate::models::{CreateTodo, Todo, UpdateTodo};
|
||||||
|
|
||||||
|
/// Luo uusi tehtävä.
|
||||||
|
pub async fn create_todo(
|
||||||
|
State(pool): State<SqlitePool>,
|
||||||
|
Json(input): Json<CreateTodo>,
|
||||||
|
) -> Result<(StatusCode, Json<Todo>), StatusCode> {
|
||||||
|
let priority = input.priority.unwrap_or(1);
|
||||||
|
let status = input.status.unwrap_or_else(|| "pending".to_string());
|
||||||
|
|
||||||
|
let result = sqlx::query_as::<_, Todo>(
|
||||||
|
"INSERT INTO todos (title, description, due_date, priority, status)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
RETURNING id, title, description, due_date, priority, status",
|
||||||
|
)
|
||||||
|
.bind(&input.title)
|
||||||
|
.bind(&input.description)
|
||||||
|
.bind(&input.due_date)
|
||||||
|
.bind(priority)
|
||||||
|
.bind(&status)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
Ok((StatusCode::CREATED, Json(result)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listaa kaikki tehtävät.
|
||||||
|
pub async fn list_todos(
|
||||||
|
State(pool): State<SqlitePool>,
|
||||||
|
) -> Result<Json<Vec<Todo>>, StatusCode> {
|
||||||
|
let todos = sqlx::query_as::<_, Todo>("SELECT id, title, description, due_date, priority, status FROM todos")
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
Ok(Json(todos))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hae tehtävä id:llä.
|
||||||
|
pub async fn get_todo(
|
||||||
|
State(pool): State<SqlitePool>,
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
) -> Result<Json<Todo>, StatusCode> {
|
||||||
|
let todo = sqlx::query_as::<_, Todo>(
|
||||||
|
"SELECT id, title, description, due_date, priority, status FROM todos WHERE id = ?",
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_optional(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
match todo {
|
||||||
|
Some(t) => Ok(Json(t)),
|
||||||
|
None => Err(StatusCode::NOT_FOUND),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Päivitä tehtävä id:llä.
|
||||||
|
pub async fn update_todo(
|
||||||
|
State(pool): State<SqlitePool>,
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
Json(input): Json<UpdateTodo>,
|
||||||
|
) -> Result<Json<Todo>, StatusCode> {
|
||||||
|
let existing = sqlx::query_as::<_, Todo>(
|
||||||
|
"SELECT id, title, description, due_date, priority, status FROM todos WHERE id = ?",
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_optional(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
let existing = existing.ok_or(StatusCode::NOT_FOUND)?;
|
||||||
|
|
||||||
|
let title = input.title.unwrap_or(existing.title);
|
||||||
|
let description = input.description.or(existing.description);
|
||||||
|
let due_date = input.due_date.or(existing.due_date);
|
||||||
|
let priority = input.priority.unwrap_or(existing.priority);
|
||||||
|
let status = input.status.unwrap_or(existing.status);
|
||||||
|
|
||||||
|
let updated = sqlx::query_as::<_, Todo>(
|
||||||
|
"UPDATE todos SET title = ?, description = ?, due_date = ?, priority = ?, status = ?
|
||||||
|
WHERE id = ?
|
||||||
|
RETURNING id, title, description, due_date, priority, status",
|
||||||
|
)
|
||||||
|
.bind(&title)
|
||||||
|
.bind(&description)
|
||||||
|
.bind(&due_date)
|
||||||
|
.bind(priority)
|
||||||
|
.bind(&status)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
Ok(Json(updated))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poista tehtävä id:llä.
|
||||||
|
pub async fn delete_todo(
|
||||||
|
State(pool): State<SqlitePool>,
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
) -> Result<StatusCode, StatusCode> {
|
||||||
|
let result = sqlx::query("DELETE FROM todos WHERE id = ?")
|
||||||
|
.bind(id)
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
if result.rows_affected() == 0 {
|
||||||
|
return Err(StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StatusCode::NO_CONTENT)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## src/lib.rs
|
||||||
|
|
||||||
|
Kirjastomoduuli: reititin `app()` ja taulun alustus `init_db()` — julkinen API integraatiotesteille.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! Kirjastomoduuli — julkinen API integraatiotesteille.
|
||||||
|
|
||||||
|
pub mod handlers;
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
|
use axum::routing::{delete, get, post, put};
|
||||||
|
use axum::Router;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
use tower_http::cors::CorsLayer;
|
||||||
|
|
||||||
|
/// Luo reititin annetulla tietokantapoolilla.
|
||||||
|
pub fn app(pool: SqlitePool) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/todos", post(handlers::create_todo))
|
||||||
|
.route("/todos", get(handlers::list_todos))
|
||||||
|
.route("/todos/{id}", get(handlers::get_todo))
|
||||||
|
.route("/todos/{id}", put(handlers::update_todo))
|
||||||
|
.route("/todos/{id}", delete(handlers::delete_todo))
|
||||||
|
.layer(CorsLayer::permissive())
|
||||||
|
.with_state(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alusta tietokantataulu.
|
||||||
|
pub async fn init_db(pool: &SqlitePool) {
|
||||||
|
sqlx::query(
|
||||||
|
"CREATE TABLE IF NOT EXISTS todos (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
due_date TEXT,
|
||||||
|
priority INTEGER NOT NULL DEFAULT 1,
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending'
|
||||||
|
)",
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.expect("Taulun luonti epäonnistui");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## src/main.rs
|
||||||
|
|
||||||
|
Käynnistyspiste: SQLite-pooli, taulun alustus, Axum-palvelin portissa 3000.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! Axum CRUD — yksi endpoint-setti per entiteetti, SQLite-tietokanta.
|
||||||
|
|
||||||
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
|
use todo_rs::{app, init_db};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.connect("sqlite:./app.db?mode=rwc")
|
||||||
|
.await
|
||||||
|
.expect("Tietokantayhteys epäonnistui");
|
||||||
|
|
||||||
|
init_db(&pool).await;
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
||||||
|
.await
|
||||||
|
.expect("Portin kuuntelu epäonnistui");
|
||||||
|
|
||||||
|
println!("Palvelin käynnissä: http://127.0.0.1:3000");
|
||||||
|
axum::serve(listener, app(pool)).await.unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## tests/api_test.rs
|
||||||
|
|
||||||
|
Integraatiotestit: muistinvarainen SQLite, `spawn_server` satunnaisportissa, uniikki suomenkielinen data per testi.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
//! Integraatiotestit — muistinvarainen SQLite, uniikki data per testi.
|
||||||
|
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use reqwest::Client;
|
||||||
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
|
use todo_rs::{app, init_db};
|
||||||
|
|
||||||
|
/// Käynnistä testipalvelin satunnaisessa portissa.
|
||||||
|
async fn spawn_server() -> (Client, String) {
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.max_connections(1)
|
||||||
|
.connect("sqlite::memory:")
|
||||||
|
.await
|
||||||
|
.expect("Testitietokanta epäonnistui");
|
||||||
|
|
||||||
|
init_db(&pool).await;
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
|
||||||
|
.await
|
||||||
|
.expect("Testiportin kuuntelu epäonnistui");
|
||||||
|
let addr = listener.local_addr().unwrap();
|
||||||
|
let base_url = format!("http://{addr}");
|
||||||
|
|
||||||
|
let router = app(pool);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
axum::serve(listener, router).await.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
(Client::new(), base_url)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_todo() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.post(format!("{url}/todos"))
|
||||||
|
.json(&serde_json::json!({"title": "Osta maitoa", "priority": 2}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::CREATED);
|
||||||
|
let body: serde_json::Value = res.json().await.unwrap();
|
||||||
|
assert_eq!(body["title"], "Osta maitoa");
|
||||||
|
assert_eq!(body["priority"], 2);
|
||||||
|
assert!(body["id"].is_number());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_todo_defaults() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.post(format!("{url}/todos"))
|
||||||
|
.json(&serde_json::json!({"title": "Oletusarvotesti"}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::CREATED);
|
||||||
|
let body: serde_json::Value = res.json().await.unwrap();
|
||||||
|
assert_eq!(body["priority"], 1);
|
||||||
|
assert_eq!(body["status"], "pending");
|
||||||
|
assert!(body["description"].is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_list_todos() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
client
|
||||||
|
.post(format!("{url}/todos"))
|
||||||
|
.json(&serde_json::json!({"title": "Listattava tehtävä"}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = client.get(format!("{url}/todos")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let body: Vec<serde_json::Value> = res.json().await.unwrap();
|
||||||
|
assert!(body.len() >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_todo_by_id() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
let created: serde_json::Value = client
|
||||||
|
.post(format!("{url}/todos"))
|
||||||
|
.json(&serde_json::json!({"title": "Haettava tehtävä"}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let id = created["id"].as_i64().unwrap();
|
||||||
|
let res = client
|
||||||
|
.get(format!("{url}/todos/{id}"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
let body: serde_json::Value = res.json().await.unwrap();
|
||||||
|
assert_eq!(body["id"], id);
|
||||||
|
assert_eq!(body["title"], "Haettava tehtävä");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_todo_not_found() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.get(format!("{url}/todos/99999"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update_todo() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
let created: serde_json::Value = client
|
||||||
|
.post(format!("{url}/todos"))
|
||||||
|
.json(&serde_json::json!({"title": "Vanha otsikko"}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let id = created["id"].as_i64().unwrap();
|
||||||
|
let res = client
|
||||||
|
.put(format!("{url}/todos/{id}"))
|
||||||
|
.json(&serde_json::json!({"title": "Uusi otsikko"}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::OK);
|
||||||
|
let body: serde_json::Value = res.json().await.unwrap();
|
||||||
|
assert_eq!(body["title"], "Uusi otsikko");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_update_todo_not_found() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.put(format!("{url}/todos/99999"))
|
||||||
|
.json(&serde_json::json!({"title": "Ei löydy"}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_todo() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
let created: serde_json::Value = client
|
||||||
|
.post(format!("{url}/todos"))
|
||||||
|
.json(&serde_json::json!({"title": "Poistettava"}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let id = created["id"].as_i64().unwrap();
|
||||||
|
let res = client
|
||||||
|
.delete(format!("{url}/todos/{id}"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.get(format!("{url}/todos/{id}"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_todo_not_found() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.delete(format!("{url}/todos/99999"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_full_lifecycle() {
|
||||||
|
let (client, url) = spawn_server().await;
|
||||||
|
|
||||||
|
// Luo
|
||||||
|
let created: serde_json::Value = client
|
||||||
|
.post(format!("{url}/todos"))
|
||||||
|
.json(&serde_json::json!({
|
||||||
|
"title": "Elinkaaritesti",
|
||||||
|
"description": "Testataan koko CRUD-kierto",
|
||||||
|
"due_date": "2026-12-31",
|
||||||
|
"priority": 3,
|
||||||
|
"status": "in_progress"
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let id = created["id"].as_i64().unwrap();
|
||||||
|
assert_eq!(created["title"], "Elinkaaritesti");
|
||||||
|
assert_eq!(created["description"], "Testataan koko CRUD-kierto");
|
||||||
|
assert_eq!(created["due_date"], "2026-12-31");
|
||||||
|
assert_eq!(created["priority"], 3);
|
||||||
|
assert_eq!(created["status"], "in_progress");
|
||||||
|
|
||||||
|
// Päivitä
|
||||||
|
let updated: serde_json::Value = client
|
||||||
|
.put(format!("{url}/todos/{id}"))
|
||||||
|
.json(&serde_json::json!({"status": "done"}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(updated["status"], "done");
|
||||||
|
assert_eq!(updated["title"], "Elinkaaritesti");
|
||||||
|
|
||||||
|
// Poista
|
||||||
|
let res = client
|
||||||
|
.delete(format!("{url}/todos/{id}"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -25,7 +25,7 @@ src/lib.rs:
|
|||||||
pub fn app(pool: SqlitePool) -> Router
|
pub fn app(pool: SqlitePool) -> Router
|
||||||
pub async fn init_db(pool: &SqlitePool) → CREATE TABLE IF NOT EXISTS
|
pub async fn init_db(pool: &SqlitePool) → CREATE TABLE IF NOT EXISTS
|
||||||
Routes: .route("/{table}", post(create).get(list))
|
Routes: .route("/{table}", post(create).get(list))
|
||||||
.route("/{table}/:id", get(get_one).put(update).delete(delete_one))
|
.route("/{table}/{id}", get(get_one).put(update).delete(delete_one))
|
||||||
|
|
||||||
src/main.rs:
|
src/main.rs:
|
||||||
SqlitePool::connect("sqlite:./app.db"), init_db, bind 0.0.0.0:3000
|
SqlitePool::connect("sqlite:./app.db"), init_db, bind 0.0.0.0:3000
|
||||||
|
|||||||
Reference in New Issue
Block a user