mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 15:24:52 +09:00
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>
This commit is contained in:
1 parent
5bea930d05
commit
2235294c59
60 files changed
+3371
-1227
No files matched your search
@@ -0,0 +1,145 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const { HostDetails } = require("@clusterio/lib");
|
||||
|
||||
const { testMatrix, testRoundTripJsonSerialisable } = require("../../common");
|
||||
|
||||
describe("lib/data/HostDetails", function () {
|
||||
|
||||
it("should be round trip serialisable", function () {
|
||||
testRoundTripJsonSerialisable(HostDetails, testMatrix(
|
||||
["1.0.0"], // version (required)
|
||||
["TestHost"], // name (required)
|
||||
[1], // id (required)
|
||||
[true, false], // connected (required)
|
||||
[undefined, "10.0.0.1"], // remoteAddress
|
||||
[undefined, "203.0.113.1"], // publicAddress
|
||||
[undefined, 123456], // tokenValidAfter (sec)
|
||||
[undefined, 999999], // updatedAtMs (ms)
|
||||
[undefined, true], // isDeleted
|
||||
));
|
||||
});
|
||||
|
||||
describe("constructor", function () {
|
||||
it("should set defaults correctly", function () {
|
||||
const host = new HostDetails("1.0.0", "TestHost", 1, true);
|
||||
|
||||
assert.equal(host.version, "1.0.0");
|
||||
assert.equal(host.name, "TestHost");
|
||||
assert.equal(host.id, 1);
|
||||
assert.equal(host.connected, true);
|
||||
assert.equal(host.remoteAddress, "");
|
||||
assert.equal(host.publicAddress, "");
|
||||
assert.equal(host.tokenValidAfter, 0);
|
||||
assert.equal(host.updatedAtMs, 0);
|
||||
assert.equal(host.isDeleted, false);
|
||||
});
|
||||
|
||||
it("should accept all constructor parameters", function () {
|
||||
const host = new HostDetails(
|
||||
"2.0.0", "FullHost", 42,
|
||||
false, "10.1.1.1", "198.51.100.5",
|
||||
123, 999, true
|
||||
);
|
||||
|
||||
assert.equal(host.version, "2.0.0");
|
||||
assert.equal(host.name, "FullHost");
|
||||
assert.equal(host.id, 42);
|
||||
assert.equal(host.connected, false);
|
||||
assert.equal(host.remoteAddress, "10.1.1.1");
|
||||
assert.equal(host.publicAddress, "198.51.100.5");
|
||||
assert.equal(host.tokenValidAfter, 123);
|
||||
assert.equal(host.updatedAtMs, 999);
|
||||
assert.equal(host.isDeleted, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".toJSON()", function () {
|
||||
it("should only include required fields when optional fields are not given", function () {
|
||||
const host = new HostDetails("1.0.0", "TestHost", 1, false);
|
||||
|
||||
assert.deepEqual(host.toJSON(), {
|
||||
version: "1.0.0",
|
||||
name: "TestHost",
|
||||
id: 1,
|
||||
connected: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("should include all optional fields when set", function () {
|
||||
const host = new HostDetails(
|
||||
"2.0.0", "FullHost", 99,
|
||||
true, "10.0.0.5", "203.0.113.5",
|
||||
500, 1000, true
|
||||
);
|
||||
|
||||
assert.deepEqual(host.toJSON(), {
|
||||
version: "2.0.0",
|
||||
name: "FullHost",
|
||||
id: 99,
|
||||
connected: true,
|
||||
remoteAddress: "10.0.0.5",
|
||||
publicAddress: "203.0.113.5",
|
||||
tokenValidAfter: 500,
|
||||
updatedAtMs: 1000,
|
||||
isDeleted: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should omit optional fields when equal to defaults", function () {
|
||||
const host = new HostDetails(
|
||||
"1.0.0", "TestHost", 1,
|
||||
true, "", "",
|
||||
0, 0, false
|
||||
);
|
||||
|
||||
assert.deepEqual(host.toJSON(), {
|
||||
version: "1.0.0",
|
||||
name: "TestHost",
|
||||
id: 1,
|
||||
connected: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("static fromJSON()", function () {
|
||||
it("should construct minimal object", function () {
|
||||
const host = HostDetails.fromJSON({
|
||||
version: "1.0.0",
|
||||
name: "TestHost",
|
||||
id: 1,
|
||||
connected: true,
|
||||
});
|
||||
|
||||
assert.equal(host.version, "1.0.0");
|
||||
assert.equal(host.name, "TestHost");
|
||||
assert.equal(host.id, 1);
|
||||
assert.equal(host.connected, true);
|
||||
assert.equal(host.remoteAddress, "");
|
||||
assert.equal(host.publicAddress, "");
|
||||
assert.equal(host.tokenValidAfter, 0);
|
||||
assert.equal(host.updatedAtMs, 0);
|
||||
assert.equal(host.isDeleted, false);
|
||||
});
|
||||
|
||||
it("should preserve optional fields exactly when provided", function () {
|
||||
const host = HostDetails.fromJSON({
|
||||
version: "3.0.0",
|
||||
name: "RemoteHost",
|
||||
id: 7,
|
||||
connected: false,
|
||||
remoteAddress: "192.168.1.5",
|
||||
publicAddress: "198.51.100.10",
|
||||
tokenValidAfter: 100,
|
||||
updatedAtMs: 2000,
|
||||
isDeleted: true,
|
||||
});
|
||||
|
||||
assert.equal(host.remoteAddress, "192.168.1.5");
|
||||
assert.equal(host.publicAddress, "198.51.100.10");
|
||||
assert.equal(host.tokenValidAfter, 100);
|
||||
assert.equal(host.updatedAtMs, 2000);
|
||||
assert.equal(host.isDeleted, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const { InstanceDetails } = require("@clusterio/lib");
|
||||
|
||||
const { testMatrix, testRoundTripJsonSerialisable } = require("../../common");
|
||||
|
||||
describe("lib/data/InstanceDetails", function () {
|
||||
|
||||
it("should be round trip serialisable", function () {
|
||||
testRoundTripJsonSerialisable(InstanceDetails, testMatrix(
|
||||
["TestInstance"], // name (required)
|
||||
[1], // id (required)
|
||||
[undefined, 5], // assignedHost
|
||||
[undefined, 34197], // gamePort
|
||||
["running", "stopped"], // status (required)
|
||||
[undefined, "1.0.0"], // factorioVersion
|
||||
[undefined, 1234], // startedAtMs
|
||||
[undefined, 9999], // updatedAtMs
|
||||
[undefined, true], // excludeFromStartAll
|
||||
));
|
||||
});
|
||||
|
||||
describe("constructor", function () {
|
||||
it("should set defaults correctly", function () {
|
||||
const instance = new InstanceDetails("TestInstance", 1, undefined, undefined, "running");
|
||||
|
||||
assert.equal(instance.name, "TestInstance");
|
||||
assert.equal(instance.id, 1);
|
||||
assert.equal(instance.assignedHost, undefined);
|
||||
assert.equal(instance.gamePort, undefined);
|
||||
assert.equal(instance.status, "running");
|
||||
assert.equal(instance.factorioVersion, undefined);
|
||||
assert.equal(instance.startedAtMs, 0);
|
||||
assert.equal(instance.updatedAtMs, 0);
|
||||
assert.equal(instance.excludeFromStartAll, false);
|
||||
});
|
||||
|
||||
it("should accept all constructor parameters", function () {
|
||||
const instance = new InstanceDetails(
|
||||
"FullInstance", 42, 10, 34197,
|
||||
"starting", "1.0.0", 1000, 2000, true
|
||||
);
|
||||
|
||||
assert.equal(instance.name, "FullInstance");
|
||||
assert.equal(instance.id, 42);
|
||||
assert.equal(instance.assignedHost, 10);
|
||||
assert.equal(instance.gamePort, 34197);
|
||||
assert.equal(instance.status, "starting");
|
||||
assert.equal(instance.factorioVersion, "1.0.0");
|
||||
assert.equal(instance.startedAtMs, 1000);
|
||||
assert.equal(instance.updatedAtMs, 2000);
|
||||
assert.equal(instance.excludeFromStartAll, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe(".toJSON()", function () {
|
||||
it("should only include required fields when optional fields are not given", function () {
|
||||
const instance = new InstanceDetails("TestInstance", 1, undefined, undefined, "running");
|
||||
|
||||
assert.deepEqual(instance.toJSON(), {
|
||||
name: "TestInstance",
|
||||
id: 1,
|
||||
status: "running",
|
||||
});
|
||||
});
|
||||
|
||||
it("should include all optional fields when set", function () {
|
||||
const instance = new InstanceDetails(
|
||||
"FullInstance", 99, 5, 34197,
|
||||
"running", "1.0.0", 500, 1000, true
|
||||
);
|
||||
|
||||
assert.deepEqual(instance.toJSON(), {
|
||||
name: "FullInstance",
|
||||
id: 99,
|
||||
status: "running",
|
||||
assignedHost: 5,
|
||||
gamePort: 34197,
|
||||
factorioVersion: "1.0.0",
|
||||
startedAtMs: 500,
|
||||
updatedAtMs: 1000,
|
||||
excludeFromStartAll: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should omit optional fields when equal to defaults", function () {
|
||||
const instance = new InstanceDetails(
|
||||
"TestInstance", 1, undefined, undefined,
|
||||
"stopped", undefined, 0, 0, false
|
||||
);
|
||||
|
||||
assert.deepEqual(instance.toJSON(), {
|
||||
name: "TestInstance",
|
||||
id: 1,
|
||||
status: "stopped",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("static fromJSON()", function () {
|
||||
it("should construct minimal object and apply constructor defaults", function () {
|
||||
const instance = InstanceDetails.fromJSON({
|
||||
name: "TestInstance",
|
||||
id: 1,
|
||||
status: "running",
|
||||
});
|
||||
|
||||
assert.equal(instance.name, "TestInstance");
|
||||
assert.equal(instance.id, 1);
|
||||
assert.equal(instance.assignedHost, undefined);
|
||||
assert.equal(instance.gamePort, undefined);
|
||||
assert.equal(instance.factorioVersion, undefined);
|
||||
assert.equal(instance.startedAtMs, 0);
|
||||
assert.equal(instance.updatedAtMs, 0);
|
||||
assert.equal(instance.excludeFromStartAll, false);
|
||||
});
|
||||
|
||||
it("should preserve optional fields exactly when provided", function () {
|
||||
const instance = InstanceDetails.fromJSON({
|
||||
name: "RemoteInstance",
|
||||
id: 7,
|
||||
status: "starting",
|
||||
assignedHost: 2,
|
||||
gamePort: 34197,
|
||||
factorioVersion: "1.0.0",
|
||||
startedAtMs: 100,
|
||||
updatedAtMs: 200,
|
||||
excludeFromStartAll: true,
|
||||
});
|
||||
|
||||
assert.equal(instance.assignedHost, 2);
|
||||
assert.equal(instance.gamePort, 34197);
|
||||
assert.equal(instance.factorioVersion, "1.0.0");
|
||||
assert.equal(instance.startedAtMs, 100);
|
||||
assert.equal(instance.updatedAtMs, 200);
|
||||
assert.equal(instance.excludeFromStartAll, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("get .isDeleted", function () {
|
||||
it("should return true when status is 'deleted'", function () {
|
||||
const instance = new InstanceDetails("TestInstance", 1, undefined, undefined, "deleted");
|
||||
|
||||
assert.equal(instance.isDeleted, true);
|
||||
});
|
||||
|
||||
it("should return false when status is not 'deleted'", function () {
|
||||
const instance = new InstanceDetails("TestInstance", 1, undefined, undefined, "running");
|
||||
|
||||
assert.equal(instance.isDeleted, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
"use strict";
|
||||
const assert = require("assert").strict;
|
||||
const lib = require("@clusterio/lib");
|
||||
|
||||
|
||||
describe("lib/data/User", function() {
|
||||
describe("class User", function() {
|
||||
it("should round trip serialize", function() {
|
||||
function test_roundtrip(serialized) {
|
||||
let user = lib.User.fromJSON(serialized);
|
||||
let user_serialized = user.toJSON();
|
||||
assert.deepEqual(user_serialized, serialized);
|
||||
let user_deserialized = lib.User.fromJSON(user_serialized);
|
||||
assert.deepEqual(user_deserialized, user);
|
||||
}
|
||||
|
||||
test_roundtrip({ name: "admin", roles: [1] });
|
||||
test_roundtrip({ name: "user", is_admin: true, is_whitelisted: true });
|
||||
test_roundtrip({ name: "user", is_banned: true, ban_reason: "Bad user" });
|
||||
test_roundtrip({ name: "user", instance_stats: [[1, { join_count: 1 }]]});
|
||||
});
|
||||
it("should keep invalid roles", function() {
|
||||
let user = lib.User.fromJSON({ name: "test", roles: [1, 4, 55] });
|
||||
assert.equal(user.roleIds.size, 3, "Unexpected count of roles");
|
||||
});
|
||||
it("should calculate playerStats", function() {
|
||||
let user = lib.User.fromJSON({ name: "test", roles: [1], instance_stats: [
|
||||
[1, {
|
||||
join_count: 1,
|
||||
online_time_ms: 60e3,
|
||||
last_join_at_ms: new Date("2020-05T12:00Z").getTime(),
|
||||
last_leave_at_ms: new Date("2020-05T12:01Z").getTime(),
|
||||
last_leave_reason: "quit",
|
||||
}],
|
||||
[2, {
|
||||
join_count: 1,
|
||||
online_time_ms: 0,
|
||||
first_join_at_ms: new Date("2020-05T12:00Z").getTime(),
|
||||
last_join_at_ms: new Date("2020-05T12:02Z").getTime(),
|
||||
}],
|
||||
]});
|
||||
|
||||
assert.equal(user.playerStats.onlineTimeMs, 60e3);
|
||||
assert.equal(user.playerStats.joinCount, 2);
|
||||
assert.deepEqual(user.playerStats.firstJoinAt, new Date("2020-05T12:00Z"));
|
||||
assert.deepEqual(user.playerStats.lastJoinAt, new Date("2020-05T12:02Z"));
|
||||
assert.deepEqual(user.playerStats.lastLeaveAt, new Date("2020-05T12:01Z"));
|
||||
assert.equal(user.playerStats.lastLeaveReason, "quit");
|
||||
|
||||
user.instanceStats.set(
|
||||
3,
|
||||
lib.PlayerStats.fromJSON({
|
||||
join_count: 2,
|
||||
online_time_ms: 120e3,
|
||||
first_join_at_ms: new Date("2020-05T11:00Z").getTime(),
|
||||
last_join_at_ms: new Date("2020-05T13:00Z").getTime(),
|
||||
last_leave_at_ms: new Date("2020-05T13:02Z").getTime(),
|
||||
last_leave_reason: "afk",
|
||||
}),
|
||||
);
|
||||
|
||||
user.recalculatePlayerStats();
|
||||
assert.equal(user.playerStats.onlineTimeMs, 180e3);
|
||||
assert.equal(user.playerStats.joinCount, 4);
|
||||
assert.deepEqual(user.playerStats.firstJoinAt, new Date("2020-05T11:00Z"));
|
||||
assert.deepEqual(user.playerStats.lastJoinAt, new Date("2020-05T13:00Z"));
|
||||
assert.deepEqual(user.playerStats.lastLeaveAt, new Date("2020-05T13:02Z"));
|
||||
assert.equal(user.playerStats.lastLeaveReason, "afk");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,267 @@
|
||||
"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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user