Files
factorio-clusterio/test/lib/data/UserDetails.test.js
Cooldude2606andHornwitser 2235294c59 Refactor user and instance manager (#837)
* Refactor UserManager to use Datastore

* Rename lib.User to UserDetails also adds to/fromUserDetails

* Fix existing tests

* Add user class tests

* Add tests for finaliseUsers

* Move HostDetails to own file with tests

* Move InstanceDetails to its own file with tests

* Rename HostInfo and add tests

* Rename InstanceInfo and add tests

- Also changes the names of the host properties related to instances
- Also removes the need to specify the location of InstanceInfo

* Make json round trip test validate schema

* Add InstanceManager

* Fix lint errors

* Extra tests

* Add final test coverage

* Add one last test to InstanceManager

* Response to comments

* Use generator rather than return anonumous generator

Co-authored-by: Hornwitser <github@hornwitser.no>

* Fix lint issue with generator

---------

Co-authored-by: Hornwitser <github@hornwitser.no>
2026-03-01 23:04:37 +00:00

268 lines
8.0 KiB
JavaScript

"use strict";
const assert = require("assert").strict;
const { UserDetails, PlayerStats } = require("@clusterio/lib");
const { testMatrix, testRoundTripJsonSerialisable } = require("../../common");
describe("lib/data/UserDetails", function () {
it("should be round trip serialisable", function () {
testRoundTripJsonSerialisable(UserDetails, testMatrix(
["TestUser"], // name (required)
[undefined, new Set([1])], // roleIds
[undefined, new Set([2])], // instances
[undefined, true], // isAdmin
[undefined, true], // isBanned
[undefined, true], // isWhitelisted
[undefined, "reason"], // banReason
[undefined, 123], // updatedAtMs
[undefined, true], // isDeleted
[ // instanceStats
undefined,
new Map([
[1, PlayerStats.fromJSON({ join_count: 1, online_time_ms: 100 })],
]),
],
));
});
describe("constructor", function () {
it("should set defaults correctly", function () {
const user = new UserDetails("TestUser");
assert.equal(user.name, "TestUser");
assert.equal(user.roleIds.size, 0);
assert.equal(user.instances.size, 0);
assert.equal(user.isAdmin, false);
assert.equal(user.isBanned, false);
assert.equal(user.isWhitelisted, false);
assert.equal(user.banReason, "");
assert.equal(user.updatedAtMs, 0);
assert.equal(user.isDeleted, false);
assert(user.playerStats instanceof PlayerStats);
assert(user.instanceStats instanceof Map);
});
it("should accept all constructor parameters", function () {
const user = new UserDetails(
"TestUser", new Set([1]), new Set([2]),
true, true, true, "reason",
100, true,
new Map()
);
assert.equal(user.name, "TestUser");
assert.deepEqual(user.roleIds, new Set([1]));
assert.deepEqual(user.instances, new Set([2]));
assert.equal(user.isAdmin, true);
assert.equal(user.isBanned, true);
assert.equal(user.isWhitelisted, true);
assert.equal(user.banReason, "reason");
assert.equal(user.updatedAtMs, 100);
assert.equal(user.isDeleted, true);
});
});
describe("get .id", function () {
it("should return lowercase name", function () {
const user = new UserDetails("AdMiN");
assert.equal(user.id, "admin");
});
it("should reflect name changes dynamically", function () {
const user = new UserDetails("User");
user.name = "NEWNAME";
assert.equal(user.id, "newname");
});
});
describe(".toJSON()", function () {
it("should only include required fields when optional fields are not given", function () {
const user = new UserDetails("Test");
assert.deepEqual(user.toJSON(), { name: "Test" });
});
it("should include all optional fields when set", function () {
const stats = new PlayerStats({ join_count: 1 });
const user = new UserDetails(
"Test", new Set([1, 2]), new Set([10]),
true, true, true, "reason",
123, true,
new Map([[1, stats]])
);
assert.deepEqual(user.toJSON(), {
name: "Test",
roles: [1, 2],
instances: [10],
is_admin: true,
is_banned: true,
is_whitelisted: true,
ban_reason: "reason",
updated_at_ms: 123,
is_deleted: true,
instance_stats: [[1, stats.toJSON()]],
});
});
it("should omit optional fields when equal to defaults", function () {
const user = new UserDetails(
"Test", new Set(), new Set(),
false, false, false, "",
0, false,
new Map()
);
assert.deepEqual(user.toJSON(), { name: "Test" });
});
});
describe("static fromJSON()", function () {
it("should construct minimal object", function () {
const user = UserDetails.fromJSON({ name: "Test" });
assert.equal(user.name, "Test");
assert.deepEqual(user.roleIds, new Set());
assert.deepEqual(user.instances, new Set());
assert.deepEqual(user.isAdmin, false);
assert.equal(user.isBanned, false);
assert.equal(user.isWhitelisted, false);
assert.equal(user.banReason, "");
assert.equal(user.updatedAtMs, 0);
assert.equal(user.isDeleted, false);
assert.deepEqual(user.instanceStats, new Map());
});
it("should preserve optional fields exactly when provided", function () {
const stats = new PlayerStats({ join_count: 1 });
const user = UserDetails.fromJSON({
name: "Test",
roles: [1, 2],
instances: [10],
is_admin: true,
is_banned: true,
is_whitelisted: true,
ban_reason: "reason",
updated_at_ms: 123,
is_deleted: true,
instance_stats: [[1, stats.toJSON()]],
});
assert.equal(user.name, "Test");
assert.deepEqual(user.roleIds, new Set([1, 2]));
assert.deepEqual(user.instances, new Set([10]));
assert.deepEqual(user.isAdmin, true);
assert.equal(user.isBanned, true);
assert.equal(user.isWhitelisted, true);
assert.equal(user.banReason, "reason");
assert.equal(user.updatedAtMs, 123);
assert.equal(user.isDeleted, true);
assert.deepEqual(user.instanceStats, new Map([[1, stats]]));
});
it("should preserve roles exactly (including invalid ones)", function () {
const user = UserDetails.fromJSON({
name: "Test",
roles: [1, 4, 55],
});
assert.equal(user.roleIds.size, 3);
assert(user.roleIds.has(55));
});
it("should reconstruct instanceStats and calculate playerStats", function () {
const json = {
name: "Test",
instance_stats: [
[1, { join_count: 1, online_time_ms: 100 }],
[2, { join_count: 2, online_time_ms: 200 }],
],
};
const user = UserDetails.fromJSON(json);
assert.equal(user.instanceStats.size, 2);
assert.equal(user.playerStats.joinCount, 3);
assert.equal(user.playerStats.onlineTimeMs, 300);
});
});
describe(".recalculatePlayerStats()", function () {
it("should aggregate stats correctly across multiple instances", function () {
const user = UserDetails.fromJSON({
name: "test",
instance_stats: [
[1, {
join_count: 1,
online_time_ms: 60e3,
last_join_at_ms: new Date("2020-05-05T12:00Z").getTime(),
last_leave_at_ms: new Date("2020-05-05T12:01Z").getTime(),
last_leave_reason: "quit",
}],
[2, {
join_count: 1,
online_time_ms: 0,
first_join_at_ms: new Date("2020-05-05T12:00Z").getTime(),
last_join_at_ms: new Date("2020-05-05T12:02Z").getTime(),
}],
],
});
assert.equal(user.playerStats.joinCount, 2);
assert.equal(user.playerStats.onlineTimeMs, 60e3);
user.instanceStats.set(3, PlayerStats.fromJSON({
join_count: 2,
online_time_ms: 120e3,
first_join_at_ms: new Date("2020-05-05T11:00Z").getTime(),
last_join_at_ms: new Date("2020-05-05T13:00Z").getTime(),
last_leave_at_ms: new Date("2020-05-05T13:02Z").getTime(),
last_leave_reason: "afk",
}));
user.recalculatePlayerStats();
assert.equal(user.playerStats.joinCount, 4);
assert.equal(user.playerStats.onlineTimeMs, 180e3);
});
});
describe("static _calculatePlayerStats()", function () {
it("should return empty stats for empty map", function () {
const stats = UserDetails._calculatePlayerStats(new Map());
assert.equal(stats.joinCount, 0);
assert.equal(stats.onlineTimeMs, 0);
});
it("should pick earliest firstJoin and latest joins/leaves", function () {
const a = PlayerStats.fromJSON({
first_join_at_ms: new Date("2020-01-01").getTime(),
last_join_at_ms: new Date("2020-01-02").getTime(),
last_leave_at_ms: new Date("2020-01-02").getTime(),
last_leave_reason: "quit",
join_count: 1,
online_time_ms: 100,
});
const b = PlayerStats.fromJSON({
first_join_at_ms: new Date("2019-01-01").getTime(),
last_join_at_ms: new Date("2021-01-01").getTime(),
last_leave_at_ms: new Date("2021-01-02").getTime(),
last_leave_reason: "afk",
join_count: 2,
online_time_ms: 200,
});
const stats = UserDetails._calculatePlayerStats(
new Map([[1, a], [2, b]])
);
assert.equal(stats.joinCount, 3);
assert.equal(stats.onlineTimeMs, 300);
assert.deepEqual(stats.firstJoinAt, new Date("2019-01-01"));
assert.deepEqual(stats.lastJoinAt, new Date("2021-01-01"));
assert.deepEqual(stats.lastLeaveAt, new Date("2021-01-02"));
assert.equal(stats.lastLeaveReason, "afk");
});
});
});