fix:#25 added tests and schedule updates

This commit is contained in:
Elmar Kresse
2024-04-22 16:11:13 +02:00
parent c8bcc3be94
commit 02a4360521
19 changed files with 731 additions and 203 deletions

View File

@@ -22,7 +22,6 @@ import (
v1 "htwkalender/service/fetch/v1"
v2 "htwkalender/service/fetch/v2"
"htwkalender/service/functions/time"
"htwkalender/service/ical"
"htwkalender/service/room"
"log/slog"
"net/http"
@@ -380,29 +379,4 @@ func AddRoutes(app *pocketbase.PocketBase) {
}
return nil
})
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
_, err := e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/feeds/migrate",
Handler: func(c echo.Context) error {
err := ical.MigrateFeedJson(app)
if err != nil {
slog.Error("Failed to migrate feeds: %v", err)
return c.JSON(http.StatusInternalServerError, "Failed to migrate feeds")
} else {
return c.JSON(http.StatusOK, "Migrated")
}
},
Middlewares: []echo.MiddlewareFunc{
apis.ActivityLogger(app),
apis.RequireAdminAuth(),
},
})
if err != nil {
return err
}
return nil
})
}

View File

@@ -23,6 +23,7 @@ import (
"htwkalender/service/course"
"htwkalender/service/feed"
"htwkalender/service/fetch/sport"
v1 "htwkalender/service/fetch/v1"
v2 "htwkalender/service/fetch/v2"
"htwkalender/service/functions/time"
"log/slog"
@@ -34,10 +35,21 @@ func AddSchedules(app *pocketbase.PocketBase) {
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
scheduler := cron.New()
// Every hour update all courses (5 segments - minute, hour, day, month, weekday) "0 * * * *"
// Every three hours update all courses (5 segments - minute, hour, day, month, weekday) "0 */3 * * *"
// Every 10 minutes update all courses (5 segments - minute, hour, day, month, weekday) "*/10 * * * *"
scheduler.MustAdd("updateCourse", "0 */3 * * *", func() {
// !! IMPORTANT !! CRON is based on UTC time zone so in Germany it is UTC+2 in summer and UTC+1 in winter
// Every sunday at 10pm update all courses (5 segments - minute, hour, day, month, weekday) "0 22 * * 0"
scheduler.MustAdd("updateCourses", "0 22 * * 0", func() {
slog.Info("Started updating courses schedule")
groups, err := v1.FetchSeminarGroups(app)
if err != nil {
slog.Warn("Failed to fetch seminar groups: %v", err)
}
slog.Info("Successfully fetched " + strconv.FormatInt(int64(len(groups)), 10) + " seminar groups")
})
// Every day at 5am and 5pm update all courses (5 segments - minute, hour, day, month, weekday) "0 5,17 * * *"
// In Germany it is 7am and 7pm, syllabus gets updated twice a day at German 5:00 Uhr and 17:00 Uhr
scheduler.MustAdd("updateEventsByCourse", "0 5,17 * * *", func() {
slog.Info("Started updating courses schedule")
course.UpdateCourse(app)
})
@@ -60,7 +72,7 @@ func AddSchedules(app *pocketbase.PocketBase) {
})
//fetch all events for semester and delete from remote this should be done every sunday at 2am
scheduler.MustAdd("fetchEvents", "0 2 * * 0", func() {
scheduler.MustAdd("fetchEvents", "0 22 * * 6", func() {
savedEvents, err := v2.FetchAllEventsAndSave(app, time.RealClock{})
if err != nil {
slog.Error("Failed to fetch and save events: %v", err)

View File

@@ -0,0 +1,58 @@
package events
import (
"htwkalender/model"
"testing"
)
func TestContainsEvent(t *testing.T) {
type args struct {
events model.Events
event model.Event
}
tests := []struct {
name string
args args
want bool
}{
{
name: "contains event",
args: args{
events: model.Events{
{
UUID: "934807509832475",
Name: "name",
},
},
event: model.Event{
UUID: "934807509832475",
Name: "name",
},
},
want: true,
},
{
name: "contains no event",
args: args{
events: model.Events{
{
UUID: "9991929292921912343534",
Name: "Name1",
},
},
event: model.Event{
UUID: "1111112312312312",
Name: "Name2",
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ContainsEvent(tt.args.events, tt.args.event); got != tt.want {
t.Errorf("ContainsEvent() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -24,6 +24,7 @@ import (
"htwkalender/model"
"htwkalender/service/db"
"htwkalender/service/functions"
clock "htwkalender/service/functions/time"
"io"
"log/slog"
"net/http"
@@ -81,7 +82,7 @@ func FetchAndUpdateSportEvents(app *pocketbase.PocketBase) ([]model.Event, error
}
// @TODO: delete and save events in one transaction and it only should delete events that are not in the new events list and save events that are not in the database
err = db.DeleteAllEventsByCourse(app, "Sport", functions.GetCurrentSemesterString())
err = db.DeleteAllEventsByCourse(app, "Sport", functions.GetCurrentSemesterString(clock.RealClock{}))
if err != nil {
return nil, err
}

View File

@@ -23,6 +23,8 @@ import (
"github.com/pocketbase/pocketbase/models"
"htwkalender/model"
"htwkalender/service/db"
"htwkalender/service/functions"
"htwkalender/service/functions/time"
"io"
"log/slog"
"net/http"
@@ -59,23 +61,19 @@ func getSeminarHTML(semester string) (string, error) {
func FetchSeminarGroups(app *pocketbase.PocketBase) ([]*models.Record, error) {
var groups []model.SeminarGroup
resultSummer, err := getSeminarHTML("ss")
semesterString := functions.CalculateSemesterList(time.RealClock{})
var results [2]string
var err error
if err != nil {
slog.Error("Error while fetching seminar groups for winter semester", err)
return nil, err
for i, semester := range semesterString {
results[i], err = getSeminarHTML(semester)
if err != nil {
slog.Error("Error while fetching seminar groups for: "+semester, err)
return nil, err
}
groups = append(groups, parseSeminarGroups(results[i], semester)...)
}
resultWinter, _ := getSeminarHTML("ws")
if err != nil {
slog.Error("Error while fetching seminar groups for summer semester", err)
return nil, err
}
groups = parseSeminarGroups(resultSummer, "ss")
groups = append(groups, parseSeminarGroups(resultWinter, "ws")...)
// filter duplicates
groups = removeDuplicates(groups)

View File

@@ -25,10 +25,10 @@ import (
"htwkalender/service/db"
"htwkalender/service/fetch"
v1 "htwkalender/service/fetch/v1"
"htwkalender/service/functions"
localTime "htwkalender/service/functions/time"
"log/slog"
"strings"
"time"
)
func ParseEventsFromRemote(app *pocketbase.PocketBase) (model.Events, error) {
@@ -70,7 +70,7 @@ func FetchAllEventsAndSave(app *pocketbase.PocketBase, clock localTime.Clock) ([
}
// Fetch and save events for all semesters
for _, semester := range calculateSemesterList(clock) {
for _, semester := range functions.CalculateSemesterList(clock) {
events, fetchErr := fetchAndSaveAllEventsForSemester(app, semester, stubUrl)
if fetchErr != nil {
return nil, fmt.Errorf("failed to fetch and save events for "+semester+": %w", err)
@@ -104,25 +104,6 @@ func fetchAndSaveAllEventsForSemester(
return savedRecords, err
}
func calculateSemesterList(clock localTime.Clock) []string {
summerSemester := clock.Now().Month() >= time.March && clock.Now().Month() <= time.September
winterSemester := clock.Now().Month() <= time.March || clock.Now().Month() >= time.September
if summerSemester && winterSemester {
return []string{"ss", "ws"}
}
if summerSemester {
return []string{"ss"}
}
if winterSemester {
return []string{"ws"}
}
return []string{"ss", "ws"}
}
func parseEventForOneSemester(url string) ([]model.Event, error) {
// Fetch Webpage from URL
webpage, err := fetch.GetHTML(url)

View File

@@ -18,10 +18,8 @@ package v2
import (
"htwkalender/model"
mockTime "htwkalender/service/functions/time"
"reflect"
"testing"
"time"
)
func Test_switchNameAndNotesForExam(t *testing.T) {
@@ -99,49 +97,3 @@ func Test_switchNameAndNotesForExam(t *testing.T) {
})
}
}
func Test_calculateSemesterList(t *testing.T) {
type args struct {
clock mockTime.Clock
}
tests := []struct {
name string
args args
want []string
}{
{
name: "is summer semester",
args: args{
clock: mockTime.MockClock{
NowTime: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC),
},
},
want: []string{"ss"},
},
{
name: "is winter semester",
args: args{
clock: mockTime.MockClock{
NowTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
want: []string{"ws"},
},
{
name: "is in both",
args: args{
clock: mockTime.MockClock{
NowTime: time.Date(2024, 3, 22, 0, 0, 0, 0, time.UTC),
},
},
want: []string{"ss", "ws"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := calculateSemesterList(tt.args.clock); !reflect.DeepEqual(got, tt.want) {
t.Errorf("calculateSemesterList() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -16,15 +16,32 @@
package functions
import "time"
import (
localTime "htwkalender/service/functions/time"
"time"
)
// GetCurrentSemesterString returns the current semester as string
// if current month is between 10 and 03 -> winter semester "ws"
func GetCurrentSemesterString() string {
if time.Now().Month() >= 10 || time.Now().Month() <= 3 {
func GetCurrentSemesterString(localeTime localTime.Clock) string {
if localeTime.Now().Month() >= 10 || localeTime.Now().Month() <= 3 {
return "ws"
} else {
return "ss"
}
}
func CalculateSemesterList(clock localTime.Clock) []string {
summerSemester := clock.Now().Month() >= time.March && clock.Now().Month() <= time.September
winterSemester := clock.Now().Month() <= time.March || clock.Now().Month() >= time.September
if summerSemester && !winterSemester {
return []string{"ss"}
}
if !summerSemester && winterSemester {
return []string{"ws"}
}
return []string{"ss", "ws"}
}

View File

@@ -0,0 +1,91 @@
package functions
import (
mockTime "htwkalender/service/functions/time"
"reflect"
"testing"
"time"
)
func Test_calculateSemesterList(t *testing.T) {
type args struct {
clock mockTime.Clock
}
tests := []struct {
name string
args args
want []string
}{
{
name: "is summer semester",
args: args{
clock: mockTime.MockClock{
NowTime: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC),
},
},
want: []string{"ss"},
},
{
name: "is winter semester",
args: args{
clock: mockTime.MockClock{
NowTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
want: []string{"ws"},
},
{
name: "is in both",
args: args{
clock: mockTime.MockClock{
NowTime: time.Date(2024, 3, 22, 0, 0, 0, 0, time.UTC),
},
},
want: []string{"ss", "ws"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CalculateSemesterList(tt.args.clock); !reflect.DeepEqual(got, tt.want) {
t.Errorf("calculateSemesterList() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetCurrentSemesterString(t *testing.T) {
type args struct {
localeTime mockTime.Clock
}
tests := []struct {
name string
args args
want string
}{
{
name: "is winter semester",
args: args{
localeTime: mockTime.MockClock{
NowTime: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
},
},
want: "ws",
},
{
name: "is summer semester",
args: args{
localeTime: mockTime.MockClock{
NowTime: time.Date(2024, 6, 1, 0, 0, 0, 0, time.UTC),
},
},
want: "ss",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetCurrentSemesterString(tt.args.localeTime); got != tt.want {
t.Errorf("GetCurrentSemesterString() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -48,13 +48,6 @@ func Contains(s []string, e string) bool {
return false
}
func ReplaceEmptyString(word string, replacement string) string {
if OnlyWhitespace(word) {
return replacement
}
return word
}
func HashString(s string) string {
hash := sha256.New()
hash.Write([]byte(s))

View File

@@ -17,6 +17,7 @@
package functions
import (
"reflect"
"testing"
)
@@ -96,3 +97,49 @@ func TestIsSeparator(t *testing.T) {
})
}
}
func TestContains(t *testing.T) {
type args struct {
s []string
e string
}
tests := []struct {
name string
args args
want bool
}{
{"empty slice", args{[]string{}, "a"}, false},
{"slice with one element equal", args{[]string{"a"}, "a"}, true},
{"slice with one element different", args{[]string{"a"}, "b"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Contains(tt.args.s, tt.args.e); got != tt.want {
t.Errorf("Contains() = %v, want %v", got, tt.want)
}
})
}
}
func TestSeperateRoomString(t *testing.T) {
type args struct {
rooms string
}
tests := []struct {
name string
args args
want []string
}{
{"empty string", args{""}, []string{}},
{"one room", args{"a"}, []string{"a"}},
{"two rooms", args{"a,b"}, []string{"a", "b"}},
{"two rooms with whitespace", args{"a, b"}, []string{"a", "b"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := SeperateRoomString(tt.args.rooms); !reflect.DeepEqual(got, tt.want) {
t.Errorf("SeperateRoomString() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,74 +0,0 @@
//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/>.
package ical
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
"htwkalender/model"
)
//update ical feed json
//add uuid field
//remove module name field
func MigrateFeedJson(app *pocketbase.PocketBase) error {
records, err := app.Dao().FindRecordsByFilter("feeds", "1=1", "-created", 0, 0)
if err != nil {
return err
}
for _, feed := range records {
var modules []model.FeedCollection
err := json.Unmarshal([]byte(feed.GetString("modules")), &modules)
if err != nil {
return err
}
var uuidFeedCollections []model.FeedCollection
for _, module := range modules {
uuid := searchUUIDForModule(app, module)
if uuid != "" {
uuidFeedCollections = append(uuidFeedCollections, model.FeedCollection{UUID: uuid, Name: module.Name, Course: module.Course, UserDefinedName: module.UserDefinedName})
}
}
jsonModules, _ := json.Marshal(uuidFeedCollections)
feed.Set("modules", string(jsonModules))
err = app.Dao().SaveRecord(feed)
if err != nil {
return err
}
}
return nil
}
func searchUUIDForModule(app *pocketbase.PocketBase, module model.FeedCollection) string {
var event model.Event
err := app.Dao().DB().Select("*").From("events").Where(dbx.NewExp("Name = {:name} AND course = {:course}", dbx.Params{"name": module.Name, "course": module.Course})).One(&event)
if err != nil {
return ""
}
return event.UUID
}

View File

@@ -74,6 +74,7 @@ func GetFreeRooms(app *pocketbase.PocketBase, from time.Time, to time.Time) ([]s
return freeRooms, nil
}
// Remove all rooms from the list that have events in the given time range
func removeRoomsThatHaveEvents(rooms []string, schedule []model.Event) []string {
var freeRooms []string
for _, room := range rooms {
@@ -84,6 +85,7 @@ func removeRoomsThatHaveEvents(rooms []string, schedule []model.Event) []string
return freeRooms
}
// Check if a room is in the schedule
func isRoomInSchedule(room string, schedule []model.Event) bool {
for _, event := range schedule {
if event.Course != "Sport" {

View File

@@ -191,6 +191,50 @@ func Test_isRoomInSchedule(t *testing.T) {
},
want: false,
},
{
name: "schedule event.Course is sport",
args: args{
room: "Klettergerüst",
schedule: []model.Event{
{
UUID: "903784265784639527",
Day: "Montag",
Week: "52",
Start: types.DateTime{},
End: types.DateTime{},
Name: "Hampelmann",
EventType: "S",
Prof: "Prof. Dr. Bewegung",
Rooms: "Klettergerüst",
Notes: "A apple a day keeps the doctor away",
Course: "Sport",
},
},
},
want: true,
},
{
name: "schedule event.Course is sport with different room",
args: args{
room: "HTWK Sportplatz",
schedule: []model.Event{
{
UUID: "903784265784639527",
Day: "Montag",
Week: "52",
Start: types.DateTime{},
End: types.DateTime{},
Name: "Hampelmann",
EventType: "S",
Prof: "Prof. Dr. Bewegung",
Rooms: "Klettergerüst",
Notes: "A apple a day keeps the doctor away",
Course: "Sport",
},
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {