Files
sm-bm-plugin/models/temporal_test.go
Jakub Zych 1046237771 feat: add the study data models and schema migrations
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.
2026-10-02 14:26:08 +02:00

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")
}
}