zuzu-rust 0.4.0

Rust implementation of ZuzuScript
Documentation
from test/more import *;

requires_capability( "fs" );
requires_capability( "db" );

from std/db import DB;
from std/io import Path;
from std/string import split;
from std/web import Request, Response;
from std/web/session import FileSessionHandler, DbSessionHandler;

function request_with_cookie ( cookie, scheme := "https" ) {
	let headers := new PairList();
	headers.add( "Cookie", cookie ) if cookie ≢ null;
	return new Request(
		env: {
			scheme:  scheme,
			headers: headers,
		},
	);
}

function response_cookie ( response ) {
	let out := response.finalize();
	return split( out[1].get("Set-Cookie"), ";" )[0];
}

let file_dir := Path.tempdir().child("sessions");
let file_handler := new FileSessionHandler(
	dir:     file_dir,
	secret:  "file-secret",
	max_age: 3600,
);

Request.set_session_handler(file_handler);
let file_req1 := request_with_cookie(null);
let file_session1 := file_req1.session();
file_session1{data}.set( "name", "Ada" );
is( file_session1.finalize(), file_session1, "file session finalize returns self" );
file_session1.finalize();
is( file_session1.is_finalized(), true, "file session finalize is idempotent" );

let file_cookie := response_cookie( new Response( session: file_session1 ) );
like(
	file_cookie,
	/^zzsession=[A-Za-z0-9_-]+\.[0-9a-f]+$/,
	"file session response sets signed cookie",
);

let file_req2 := request_with_cookie(file_cookie);
let file_session2 := file_req2.session();
is( file_session2.id(), file_session1.id(), "file session reloads same id" );
is( file_session2{data}{name}, "Ada", "file session reloads data" );

let tampered_cookie := file_cookie _ "x";
let file_session3 := request_with_cookie(tampered_cookie).session();
isnt( file_session3.id(), file_session1.id(), "tampered file cookie is rejected" );

let expired_handler := new FileSessionHandler(
	dir:     Path.tempdir().child("expired-sessions"),
	secret:  "file-secret",
	max_age: -1,
);
Request.set_session_handler(expired_handler);
let expired_session1 := request_with_cookie(null).session();
let expired_cookie := response_cookie( new Response(
	session: expired_session1.finalize(),
) );
let expired_session2 := request_with_cookie(expired_cookie).session();
isnt( expired_session2.id(), expired_session1.id(), "expired file session is replaced" );

let dbh := DB.temp();
let db_handler := new DbSessionHandler(
	dbh:     dbh,
	secret:  "db-secret",
	max_age: 3600,
);

Request.set_session_handler(db_handler);
let db_req1 := request_with_cookie(null);
let db_session1 := db_req1.session();
db_session1{data}.set( "colour", "blue" );
let db_cookie := response_cookie( new Response(
	session: db_session1.finalize(),
) );

let db_req2 := request_with_cookie(db_cookie);
let db_session2 := db_req2.session();
is( db_session2.id(), db_session1.id(), "db session reloads same id" );
is( db_session2{data}{colour}, "blue", "db session reloads data" );

let insecure_req := request_with_cookie(null, "http");
let insecure_session := insecure_req.session();
let insecure_cookie_out := new Response(
	session: insecure_session.finalize(),
).finalize();
unlike(
	insecure_cookie_out[1].get("Set-Cookie"),
	/; Secure/,
	"http sessions do not set Secure by default",
);

Request.set_session_handler(null);
done_testing();