1
0

implement session repository

This commit is contained in:
Adrian Wannenmacher 2026-02-15 18:09:50 +01:00
parent fb2fd76c6c
commit b89206f113
Signed by: tfld
GPG Key ID: 19D986ECB1E492D5
4 changed files with 232 additions and 0 deletions

View File

@ -48,6 +48,7 @@ export default function() {
});
});
// close db after each test
hooks.afterEach(function() {
if (inst !== null)
if (inst.open)

97
data/session_repo.js Normal file
View File

@ -0,0 +1,97 @@
"use strict";
import Session from "../models/session.js";
import WbDb from "./db.js";
/** A transaction or known type that can be turned into a transaction.
* @typedef {IDBTransaction|IDBDatabase|WbDb=} Transactable
*
* 1. If the transactable is a transaction already, it shall be used directy.
* No validation of the accessible object stores or mode should be done, the
* creator is responsible for ensuring correctness of those.
* 2. If the transactable is a database, a transaction with the provided stores
* shall be started on it.
* 3. If the transactable is an instance of `WbDb`, a transaction shall be
* started on its database.
* 4. Otherwise a transaction shall be started on the main `WbDb` instance.
*/
/** Transform a `Transactable` into a transaction.
*
* @param {Transactable} value The `Transactable` to turn into a transaction.
* @param {string[]} stores
* The object stores to acquire if the transaction has to be started.
* @param {IDBTransactionMode=} mode The transaction mode to use.
* @returns {IDBTransaction} A usable transaction.
*/
function toTransaction(value, stores, mode) {
if (value === undefined)
value = WbDb.get();
if (value instanceof WbDb)
value = value.db;
if (value instanceof IDBDatabase)
value = value.transaction(stores, mode);
if (!(value instanceof IDBTransaction))
throw new TypeError("transaction must be or become a IDBTransaction");
return value;
}
/** Turn a request into a promise.
*
* The promise fulfills if the request succeeds, and rejects if it fails.
*
* @param {IDBRequest} request The request to wrap.
* @returns A promise linked to the request.
*/
function requestToPromise(request) {
return new Promise(function(resolve, reject) {
request.onsuccess = (req) => resolve(req.target.result);
request.onerror = reject;
});
}
/** Collection of static function to interact with stored sessions. */
export default class SessionRepo {
constructor() {
throw new TypeError("SessionRepo cannot be constructed");
}
/** Put a session into the repository.
*
* If the passed session has no `id` set, the newly stored ID will be
* inserted back into it.
*
* @param {Session} session The session to store.
* @param {Transactable} transaction A transaction to use.
*
* @returns {Promise<number>} A promise containing the ID of the add session.
*/
static put(session, transaction) {
if (!(session instanceof Session))
throw new TypeError("session to put in must be an actual Session");
transaction = toTransaction(transaction, [WbDb.OS_SESSIONS], "readwrite");
let sessions = transaction.objectStore(WbDb.OS_SESSIONS);
let struct = session.toStruct();
let req = requestToPromise(sessions.put(struct));
if (session.id === null)
req.then((id) => session.id = id);
return req;
}
/** Get all sessions in the repository.
*
* @param {Transactable} transaction A transaction to use.
*
* @returns {Promise<Session[]>} A promise containing the stored sessions.
*/
static async getAll(transaction) {
transaction = toTransaction(transaction, [WbDb.OS_SESSIONS], "readonly");
let sessions = transaction.objectStore(WbDb.OS_SESSIONS);
sessions = await requestToPromise(sessions.getAll());
return sessions.map((session) => new Session(session));
}
}

132
data/session_repo.test.js Normal file
View File

@ -0,0 +1,132 @@
"use strict";
import { Team } from "../models/round.js";
import Session from "../models/session.js";
import WbDb from "./db.js";
import SessionRepo from "./session_repo.js";
/** The instance used for the current test.
*
* Put test instances of `WbDb` into this variable. If it is used, the
* connection is automatically closed after the test is done. That in turn
* means that the database can then be deleted immediately, thus speeding up
* the next test.
*
* @type {WbDb}
*/
let inst = null;
/** Wait for the `WbDb.EVENT_CHANGE` to be fired on `instance`.
*
* Uses the `inst` global variable if no `instance` parameter is passed.
*
* @param {WbDb=} instance The instance to wait on.
*/
function waitForChange(instance) {
return new Promise(function(resolve) {
(instance ?? inst).addEventListener(WbDb.EVENT_CHANGE, function() {
resolve();
});
});
}
export default function() {
QUnit.module("session_repo", function(hooks) {
// delete test database before each test
hooks.beforeEach(function() {
return new Promise(function(resolve) {
let req = indexedDB.deleteDatabase(WbDb.DB_NAME_TEST);
req.onsuccess = function() {
resolve();
};
});
});
// close db after each test
hooks.afterEach(function() {
if (inst !== null)
if (inst.open)
inst.db.close();
inst = null;
});
QUnit.test("cannot call constructor", function(assert) {
assert.throws(
function() { new SessionRepo(); },
new TypeError("SessionRepo cannot be constructed"));
});
QUnit.test("initially no sessions", async function(assert) {
inst = WbDb.get(true);
await waitForChange(inst);
let sessions = await SessionRepo.getAll(inst);
assert.strictEqual(sessions.length, 0, "no sessions");
});
QUnit.test("store single session", async function(assert) {
inst = WbDb.get(true);
let req = waitForChange(inst);
let session = new Session();
session.ourTeam = "This is us!";
session.theirTeam = "This is them!";
session.goal = 2;
session.anotherGame();
session.currentGame.currentRound.winner = Team.We;
session.anotherGame();
assert.strictEqual(session.id, null, "no initial session id");
await req;
let id = await SessionRepo.put(session, inst);
assert.strictEqual(session.id, id, "session id has been updated");
let sessions = await SessionRepo.getAll(inst);
assert.strictEqual(sessions.length, 1, "one stored session");
assert.deepEqual(
sessions[0].toStruct(), session.toStruct(), "sessions match");
});
QUnit.test("store two sessions", async function(assert) {
inst = WbDb.get(true);
let req = waitForChange(inst);
let first = new Session();
first.ourTeam = "Team A";
first.theirTeam = "Team 1";
first.goal = 2;
first.anotherGame();
first.currentGame.currentRound.winner = Team.We;
first.anotherGame();
let second = new Session();
second.ourTeam = "Team B";
second.theirTeam = "Team 2";
second.goal = 3;
second.anotherGame();
second.currentGame.currentRound.raise(Team.We);
second.currentGame.currentRound.winner = Team.They;
await req;
let putFirst = SessionRepo.put(first, inst);
let putSecond = SessionRepo.put(second, inst);
await Promise.all([putFirst, putSecond]);
let sessions = await SessionRepo.getAll(inst);
assert.strictEqual(sessions.length, 2, "two sessions stored");
assert.notStrictEqual(sessions[0].id, sessions[1].id, "IDs don't match");
for (let session of sessions) {
let expected = null;
if (session.id === first.id) {
expected = first.toStruct();
} else if (session.id === second.id) {
expected = second.toStruct();
}
assert.deepEqual(session.toStruct(), expected, "sessions match");
}
});
});
}

View File

@ -6,6 +6,7 @@ import game from "./models/game.test.js";
import session from "./models/session.test.js";
import db from "./data/db.test.js";
import session_repo from "./data/session_repo.test.js";
QUnit.module("models", function() {
round();
@ -16,4 +17,5 @@ QUnit.module("models", function() {
QUnit.module("data", function() {
db();
session_repo();
});