Files
htwkalender-pwa/frontend/src/model/roomOccupancyList.test.ts

652 lines
20 KiB
TypeScript

//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
//Copyright (C) 2024 HTWKalender support@htwkalender.de
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU Affero General Public License for more details.
//You should have received a copy of the GNU Affero General Public License
//along with this program. If not, see <https://www.gnu.org/licenses/>.
import { beforeEach, describe, expect, test } from "vitest";
import { RoomOccupancyList } from "./roomOccupancyList";
import { Binary } from "bson";
import { add, addHours, addMinutes, interval, subHours } from "date-fns";
import { fromZonedTime, toZonedTime } from "date-fns-tz";
const testListStart = new Date("2022-01-01T00:00:00Z");
var testList : RoomOccupancyList; //= RoomOccupancyList.fromJSON({});
var alternating : Uint8Array = new Uint8Array(Array(4).fill(0xF0));
var booked : Uint8Array = new Uint8Array(Array(4).fill(0xFF));
var empty : Uint8Array = new Uint8Array(Array(4).fill(0x00));
var counting : Uint8Array = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
const localTimezone = "Europe/Berlin";
describe("RoomOccupancyList", () => {
beforeEach(() => {
alternating = new Uint8Array(Array(4).fill(0xF0));
booked = new Uint8Array(Array(4).fill(0xFF));
empty = new Uint8Array(Array(4).fill(0x00));
counting = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
testList = RoomOccupancyList.fromJSON({
start: testListStart,
granularity: 60,
blocks: 32,
rooms: [
{
name: "BOOKED",
occupancy: new Binary(booked, 0),
},
{
name: "EMPTY",
occupancy: new Binary(empty, 0),
},
{
name: "ALTERNATING",
occupancy: new Binary(alternating, 0),
},
{
name: "COUNTING",
occupancy: new Binary(counting, 0),
}
]
});
});
describe("getRooms", () => {
test("get rooms", () => {
// act
const rooms = testList["getRooms"]();
// assert
expect(rooms).toEqual([
"BOOKED",
"EMPTY",
"ALTERNATING",
"COUNTING"
]);
});
test("get empty rooms", () => {
// arrange
const emptyRoomOccupancy = RoomOccupancyList.fromJSON({
start: testListStart,
granularity: 60,
blocks: 32,
rooms: []
});
// act
const rooms = emptyRoomOccupancy["getRooms"]();
// assert
expect(rooms).toEqual([]);
});
})
describe("decodeOccupancy", () => {
test("generate stubs for missing room", () => {
// arrange
// act
const decoded = testList["decodeOccupancy"](
"MISSING",
testListStart,
addHours(testListStart, 8),
);
// assert
expect(decoded).toEqual([
{
start: "2022-01-01T06:00:00.000Z",
end: "2022-01-01T08:00:00.000Z",
rooms: "MISSING",
free: true,
stub: true
}
]);
});
test("generate stubs out of range", () => {
// arrange
// act
const decoded = testList["decodeOccupancy"](
"BOOKED",
subHours(testListStart, 10),
testListStart,
);
// assert
expect(decoded).toEqual([
{
start: "2021-12-31T14:00:00.000Z",
end: "2021-12-31T19:00:00.000Z",
rooms: "BOOKED",
free: true,
stub: true
}
]);
});
test("decode occupancy in range", () => {
// arrange
// act
const decoded = testList["decodeOccupancy"](
"BOOKED",
subHours(testListStart, 2),
addHours(testListStart, 8),
);
expect(decoded).toEqual([
{
start: "2022-01-01T00:00:00.000Z",
end: "2022-01-01T08:00:00.000Z",
rooms: "BOOKED",
free: false,
stub: false
}
]);
});
});
describe("sliceOccupancy", () => {
test.each([
booked,
empty,
alternating
])("getCompleteOccupancy of %j", (occupancy) => {
// arrange
const startTime = new Date(testList.start);
const endTime = new Date(addHours(startTime, 32));
const sliceInterval = interval(startTime, endTime);
// act
const sliced = testList["sliceOccupancy"](
sliceInterval,
occupancy
)
// assert
expect(sliced).toEqual({
decodeSliceStart: startTime,
decodeSlice: new Uint8Array(occupancy),
});
});
test("throws start out of bounds", () => {
// arrange
const startTime = new Date(subHours(testList.start,1));
const endTime = new Date(addHours(startTime, 32));
const sliceInterval = interval(startTime, endTime);
// act and assert
expect(() => {
testList["sliceOccupancy"](
sliceInterval,
alternating
)
}).toThrowError();
});
test("throws end out of bounds", () => {
// arrange
const startTime = new Date(testList.start);
const endTime = new Date(addHours(startTime, 33));
const sliceInterval = interval(startTime, endTime);
// act and assert
expect(() => {
testList["sliceOccupancy"](
sliceInterval,
alternating
)
}).toThrowError();
});
test("range at byte boundaries", () => {
// arrange
const startTime = new Date(addHours(testList.start, 8));
const endTime = new Date(addHours(testList.start, 24));
const sliceInterval = interval(startTime, endTime);
// act
const sliced = testList["sliceOccupancy"](
sliceInterval,
counting
)
// assert
expect(sliced).toEqual({
decodeSliceStart: startTime,
decodeSlice: new Uint8Array([0x01, 0x02]),
});
});
test("range within byte boundaries", () => {
// arrange
const startTime = new Date(addMinutes(testListStart, 500));
const endTime = new Date(addHours(testListStart, 15));
const sliceInterval = interval(startTime, endTime);
// act
const sliced = testList["sliceOccupancy"](
sliceInterval,
counting
)
// assert
expect(sliced).toEqual({
decodeSliceStart: addHours(testListStart,8),
decodeSlice: new Uint8Array([0x01]),
});
});
});
describe("getOccupancyInterval", () => {
test("get empty interval", () => {
// arrange
const emptyRoomOccupancy = RoomOccupancyList.fromJSON({
start: testListStart,
granularity: 60,
blocks: 0,
rooms: []
});
// act
const emptyInterval = emptyRoomOccupancy["getOccupancyInterval"]();
// assert
expect(emptyInterval).toEqual(interval(testListStart, testListStart));
});
test("get interval of valid room occupancy list", () => {
// act
const testInterval = testList["getOccupancyInterval"]();
// assert
expect(testInterval).toEqual(interval(testListStart, addHours(testListStart, 32)));
});
});
describe("decodeOccupancyData", () => {
test("decode occupancy without length", () => {
// arrange
// act
const decoded = RoomOccupancyList["decodeOccupancyData"](
new Uint8Array([]),
testListStart,
15,
"Raum"
);
// assert
expect(decoded).toEqual([]);
});
test("decode blocked occupancy", () => {
// arrange
// act
const decoded = RoomOccupancyList["decodeOccupancyData"](
booked,
testListStart,
15,
"BOOKED"
);
// assert
expect(decoded).toEqual([
{
start: testListStart.toISOString(),
end: addHours(testListStart, 8).toISOString(),
rooms: "BOOKED",
free: false,
stub: false
}
]);
});
test("decode empty occupancy", () => {
// arrange
// act
const decoded = RoomOccupancyList["decodeOccupancyData"](
empty,
testListStart,
15,
"BOOKED"
);
// assert
expect(decoded).toEqual([]);
});
test("decode alternating occupancy", () => {
// arrange
// act
const decoded = RoomOccupancyList["decodeOccupancyData"](
alternating,
new Date("2024-01-01T00:00:00Z"),
15,
"ALTERNATING"
);
// assert
expect(decoded).toEqual([
{
start: "2024-01-01T00:00:00.000Z",
end: "2024-01-01T01:00:00.000Z",
rooms: "ALTERNATING",
free: false,
stub: false
},
{
start: "2024-01-01T02:00:00.000Z",
end: "2024-01-01T03:00:00.000Z",
rooms: "ALTERNATING",
free: false,
stub: false
},
{
start: "2024-01-01T04:00:00.000Z",
end: "2024-01-01T05:00:00.000Z",
rooms: "ALTERNATING",
free: false,
stub: false
},
{
start: "2024-01-01T06:00:00.000Z",
end: "2024-01-01T07:00:00.000Z",
rooms: "ALTERNATING",
free: false,
stub: false
}
]);
});
});
describe("generateStubEvents", () => {
test("no events if negative range", () => {
// arrange
const startTime = new Date("2022-01-01T00:00:00Z");
const endTime = new Date("2021-01-01T00:00:00Z");
// act
const stubEvents = RoomOccupancyList["generateStubEvents"](
"ROOM",
startTime,
endTime
);
// assert
expect(stubEvents).toEqual([]);
});
test("no events if start and end the same", () => {
// arrange
const startTime = new Date("2022-01-01T00:00:00Z");
const endTime = new Date("2022-01-01T00:00:00Z");
// act
const stubEvents = RoomOccupancyList["generateStubEvents"](
"ROOM",
startTime,
endTime
);
// assert
expect(stubEvents).toEqual([]);
});
test("generate week", () => {
// arrange
const startTime = new Date("2022-01-01T12:00:00Z");
const endTime = new Date("2022-01-07T12:30:00Z");
// act
const stubEvents = RoomOccupancyList["generateStubEvents"](
"ROOM",
startTime,
endTime
);
// assert
expect(stubEvents).toEqual([
{
start: "2022-01-01T12:00:00.000Z",
end: "2022-01-01T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-02T06:00:00.000Z",
end: "2022-01-02T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-03T06:00:00.000Z",
end: "2022-01-03T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-04T06:00:00.000Z",
end: "2022-01-04T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-05T06:00:00.000Z",
end: "2022-01-05T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-06T06:00:00.000Z",
end: "2022-01-06T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
},
{
start: "2022-01-07T06:00:00.000Z",
end: "2022-01-07T12:30:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
}
]);
});
test("generate day", () => {
// arrange
const startTime = new Date("2022-01-01T16:00:00Z");
const endTime = new Date("2022-01-01T19:00:00Z");
// act
const stubEvents = RoomOccupancyList["generateStubEvents"](
"ROOM",
startTime,
endTime
);
// assert
expect(stubEvents).toEqual([
{
start: "2022-01-01T16:00:00.000Z",
end: "2022-01-01T19:00:00.000Z",
rooms: "ROOM",
free: true,
stub: true,
}
]);
});
});
describe("shiftTimeForwardInsideWorkday", () => {
test("shift time to next day", () => {
// arrange
const startTime = new Date("2022-01-01T20:00:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-01T23:00:00Z"));
});
test("don't shift time on the same day", () => {
// arrange
const startTime = new Date("2022-01-02T01:00:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T01:00:00Z"));
});
test("don't shift if already inside workday", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T12:30:00Z"));
});
});
describe("shiftTimeBackwardInsideWorkday", () => {
test("shift time to last day", () => {
// arrange
const startTime = new Date("2022-01-02T05:30:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-01T22:59:59.999Z"));
});
test("don't shift time on the same day", () => {
// arrange
const startTime = new Date("2022-01-02T22:00:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T22:00:00Z"));
});
test("don't shift if already inside workday", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
// assert
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T12:30:00Z"));
});
});
describe("setTimeOfDay", () => {
test("set time to 00:00:00", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {});
// assert
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
});
test("set time to 23:59:59", () => {
// arrange
const startTime = new Date("2022-06-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {hours: 23, minutes: 59, seconds: 59});
// assert
expect(shiftedTime).toEqual(new Date("2022-06-02T21:59:59Z"));
});
test("set same time", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {hours: 13, minutes: 30, seconds: 0});
// assert
expect(shiftedTime).toEqual(new Date("2022-01-02T12:30:00Z"));
});
});
describe("startOfDay", () => {
test("in the winter", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
// assert
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
});
test("in the summer", () => {
// arrange
const startTime = new Date("2022-06-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
// assert
expect(shiftedTime).toEqual(new Date("2022-06-01:22:00Z"));
});
});
describe("endOfDay", () => {
test("in the winter", () => {
// arrange
const startTime = new Date("2022-01-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
// assert
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
});
test("in the summer", () => {
// arrange
const startTime = new Date("2022-06-02T12:30:00Z");
// act
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
// assert
expect(shiftedTime).toEqual(new Date("2022-06-01:22:00Z"));
});
});
});