Courses, meetings, class sessions, deadlines, readings with per-user progress, materials with tags and file attachments. Timestamps are stored in UTC and held as Europe/Warsaw text so the admin text inputs can write them.
137 lines
4.2 KiB
Go
137 lines
4.2 KiB
Go
package models_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.golem15.com/jakub/sm-bm-plugin/models"
|
|
)
|
|
|
|
func TestParseDateTimeForms(t *testing.T) {
|
|
want := time.Date(2026, 10, 3, 6, 0, 0, 0, time.UTC)
|
|
for _, in := range []string{
|
|
"2026-10-03T08:00:00+02:00",
|
|
"2026-10-03T06:00:00Z",
|
|
"2026-10-03 08:00",
|
|
"2026-10-03T08:00",
|
|
" 2026-10-03 08:00:00 ",
|
|
} {
|
|
got, err := models.ParseDateTime(in)
|
|
if err != nil {
|
|
t.Errorf("ParseDateTime(%q): %v", in, err)
|
|
continue
|
|
}
|
|
if !got.Equal(want) {
|
|
t.Errorf("ParseDateTime(%q) = %v, want %v", in, got, want)
|
|
}
|
|
}
|
|
// Winter time: a wall-clock time in December is UTC+1.
|
|
got, err := models.ParseDateTime("2026-12-01 08:00")
|
|
if err != nil || !got.Equal(time.Date(2026, 12, 1, 7, 0, 0, 0, time.UTC)) {
|
|
t.Errorf("winter wall clock = %v, %v", got, err)
|
|
}
|
|
for _, bad := range []string{"", "tomorrow", "2026-13-01 08:00", "2026-10-03"} {
|
|
if _, err := models.ParseDateTime(bad); err == nil {
|
|
t.Errorf("ParseDateTime(%q) accepted", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDateTimeRoundTrip(t *testing.T) {
|
|
d := models.DateTime("2026-10-03 08:00")
|
|
v, err := d.Value()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stored, ok := v.(time.Time)
|
|
if !ok || stored.Location() != time.UTC || stored.Hour() != 6 {
|
|
t.Fatalf("Value = %#v, want 06:00 UTC", v)
|
|
}
|
|
var read models.DateTime
|
|
if err := read.Scan(stored); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if read != "2026-10-03T08:00:00+02:00" {
|
|
t.Errorf("Scan = %q, want the Warsaw RFC 3339 form", read)
|
|
}
|
|
if err := read.Scan("2026-10-03 06:00:00+00"); err != nil || read != "2026-10-03T08:00:00+02:00" {
|
|
t.Errorf("Scan(postgres text) = %q, %v", read, err)
|
|
}
|
|
if err := read.Scan(nil); err != nil || read != "" {
|
|
t.Errorf("Scan(nil) = %q, %v", read, err)
|
|
}
|
|
if v, err := models.DateTime("").Value(); err != nil || v != nil {
|
|
t.Errorf("empty Value = %v, %v; want NULL", v, err)
|
|
}
|
|
if _, err := models.DateTime("soon").Value(); err == nil {
|
|
t.Error("an unparsable DateTime was stored")
|
|
}
|
|
if err := read.Scan(42); err == nil {
|
|
t.Error("Scan(int) succeeded")
|
|
}
|
|
}
|
|
|
|
func TestDateRoundTrip(t *testing.T) {
|
|
var d models.Date
|
|
if err := d.Scan(time.Date(2026, 10, 3, 0, 0, 0, 0, time.UTC)); err != nil || d != "2026-10-03" {
|
|
t.Errorf("Scan(time) = %q, %v", d, err)
|
|
}
|
|
if err := d.Scan([]byte("2026-10-04")); err != nil || d != "2026-10-04" {
|
|
t.Errorf("Scan(bytes) = %q, %v", d, err)
|
|
}
|
|
if v, err := models.Date("2026-10-03").Value(); err != nil || v != "2026-10-03" {
|
|
t.Errorf("Value = %v, %v", v, err)
|
|
}
|
|
if _, err := models.Date("03.10.2026").Value(); err == nil {
|
|
t.Error("a non-ISO date was stored")
|
|
}
|
|
if models.NewDate(time.Date(2026, 10, 2, 23, 30, 0, 0, time.UTC)) != "2026-10-03" {
|
|
t.Error("NewDate does not use the Warsaw calendar day")
|
|
}
|
|
}
|
|
|
|
func TestTemporalErrors(t *testing.T) {
|
|
s := &models.ClassSession{StartsAt: "2026-10-03 10:00", EndsAt: "2026-10-03 09:00"}
|
|
if errs := s.TemporalErrors(); len(errs["ends_at"]) != 1 {
|
|
t.Errorf("end before start: %v", errs)
|
|
}
|
|
s = &models.ClassSession{StartsAt: "bad"}
|
|
errs := s.TemporalErrors()
|
|
if len(errs["starts_at"]) != 1 || len(errs["ends_at"]) != 1 {
|
|
t.Errorf("bad and missing: %v", errs)
|
|
}
|
|
m := &models.Meeting{StartsOn: "2026-10-04", EndsOn: "2026-10-03"}
|
|
if errs := m.TemporalErrors(); len(errs["ends_on"]) != 1 {
|
|
t.Errorf("meeting order: %v", errs)
|
|
}
|
|
ok := &models.Meeting{StartsOn: "2026-10-03", EndsOn: "2026-10-04"}
|
|
if errs := ok.TemporalErrors(); errs != nil {
|
|
t.Errorf("valid meeting: %v", errs)
|
|
}
|
|
draft := &models.Material{}
|
|
if errs := draft.TemporalErrors(); errs != nil {
|
|
t.Errorf("draft material: %v", errs)
|
|
}
|
|
bad := models.DateTime("yesterday")
|
|
if errs := (&models.Material{PublishedAt: &bad}).TemporalErrors(); len(errs["published_at"]) != 1 {
|
|
t.Errorf("bad published_at: %v", errs)
|
|
}
|
|
}
|
|
|
|
func TestBeforeSaveBlanksOptionalText(t *testing.T) {
|
|
empty, space := "", " "
|
|
c := &models.Course{Code: &empty, Lecturer: &space}
|
|
if err := c.BeforeSave(nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.Code != nil || c.Lecturer != nil {
|
|
t.Errorf("blank text kept: %v %v", c.Code, c.Lecturer)
|
|
}
|
|
pub := models.DateTime("")
|
|
m := &models.Material{PublishedAt: &pub}
|
|
_ = m.BeforeSave(nil)
|
|
if m.PublishedAt != nil {
|
|
t.Error("an empty published_at was not cleared to a draft")
|
|
}
|
|
}
|