refactor: enhance date parsing logic and improve test coverage for ParseDettaglio function

This commit is contained in:
Marco Pedone 2025-12-16 13:28:57 +01:00
parent 94e4913a88
commit 91b1f361de
5 changed files with 247 additions and 150 deletions

View file

@ -1,4 +1,5 @@
images
videos
tmp
vscode
.vscode

View file

@ -1,5 +1,6 @@
tmp
images
videos
bkp.xml
.bin
web.config

View file

@ -260,7 +260,7 @@ func extractData_update(annuncio *typesdefs.AnnuncioXML, updateImages bool, upda
tmp.Desc_en = annuncio.DescrizioneEn
if annuncio.Dettaglio != nil {
disp, parm, pers, err := parser.ParseDettaglio(*annuncio.Dettaglio)
disp, parm, pers, err := parser.ParseDettaglio(*annuncio.Dettaglio, tmp.Consegna)
if err == nil {
tmp.Disponibile_da = disp
tmp.Permanenza = parm

View file

@ -2,38 +2,60 @@ package parser
import (
"backend/utils"
"fmt"
"regexp"
"strconv"
"strings"
"time"
)
func ParseDettaglio(dettaglio string) (disp_time *string, perm_arr *[]int, persone_arr *[]string, err error) {
func parseDispTime(disp string, currConsegna *int) *string {
if currConsegna == nil {
return nil
}
var day int
var err error
if disp != "" {
//get only the day part from disp
parts := strings.Split(disp, "/")
dayStr := strings.TrimSpace(parts[0])
day, err = strconv.Atoi(dayStr)
if err != nil || day < 1 || day > 31 {
// Invalid day, default to 1
day = 1
}
} else {
day = 1
}
month := *currConsegna
currMonth := int(time.Now().Month())
year := time.Now().Year()
if month < currMonth {
year++
}
t := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
if t.Day() != day {
// Invalid day for month, default to first day of month
t = time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
}
formatted := t.Format(time.DateOnly)
return &formatted
}
func ParseDettaglio(dettaglio string, currConsegna *int) (disp_time *string, perm_arr *[]int, persone_arr *[]string, err error) {
dettaglio = strings.TrimSpace(dettaglio)
if dettaglio == "" {
return
}
disp := getKeyValueUpToXorWhitespace(dettaglio, "disp:", "perm:")
perm := getKeyValueUpToXorWhitespace(dettaglio, "perm:", "persone:")
persone := getKeyValueUpToXorWhitespace(dettaglio, "persone:", "")
if disp != "" {
withAnno := fmt.Sprintf("%s/%d", disp, time.Now().Year())
// Validate date format
t, err := time.Parse("02/01/2006", withAnno)
if err != nil {
disp_time = nil
} else {
formatted := t.Format(time.DateOnly)
disp_time = &formatted
}
}
disp_time = parseDispTime(disp, currConsegna)
if perm != "" {
parts := strings.Split(perm, "-")
for _, part := range parts {

View file

@ -9,201 +9,274 @@ import (
)
func TestParseDettaglio(t *testing.T) {
// Helper to build expected date string
buildExpectedDate := func(day int, month int) string {
year := time.Now().Year()
currMonth := int(time.Now().Month())
if month < currMonth {
year++
}
return fmt.Sprintf("%d-%02d-%02d", year, month, day)
}
consegnaOct := utils.Ptr(10) // October
consegnaFeb := utils.Ptr(2) // February
tests := []struct {
input string
wantDisp *string
wantPerm *[]int
wantPers *[]string
name string
input string
currConsegna *int
wantDisp *string
wantPerm *[]int
wantPers *[]string
}{
{
input: "disp:01/10 perm:3-6 persone:single",
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "all fields present",
input: "disp:01/10 perm:3-6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp: perm: persone:",
wantDisp: nil,
wantPerm: nil,
wantPers: nil,
name: "all fields empty",
input: "disp: perm: persone:",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)), // defaults to 1st
wantPerm: nil,
wantPers: nil,
},
{
input: "disp: perm:3-6 persone:single", // disp missing
wantDisp: nil,
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "disp missing value",
input: "disp: perm:3-6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)), // defaults to 1st
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm: persone:single", // perm missing
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: nil,
wantPers: utils.Ptr([]string{"single"}),
name: "perm missing",
input: "disp:01/10 perm: persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: nil,
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:", // persone missing
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: nil,
name: "persone missing",
input: "disp:01/10 perm:3-6 persone:",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: nil,
},
{
input: "disp: perm: persone:", // all fields missing
wantDisp: nil,
wantPerm: nil,
wantPers: nil,
name: "invalid date - defaults to 1st",
input: "disp:bad_date perm:3-6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)), // defaults to 1st
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:bad_date perm:3-6 persone:single", // invalid date
wantDisp: nil,
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "non-numeric perm",
input: "disp:01/10 perm:abc-6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:abc-6 persone:single", // non-numeric perm
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{6}),
wantPers: utils.Ptr([]string{"single"}),
name: "trailing delimiter in perm",
input: "disp:01/10 perm:3- persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3- persone:single", // trailing delimiter in perm
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3}),
wantPers: utils.Ptr([]string{"single"}),
name: "leading delimiter in perm",
input: "disp:01/10 perm:-6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:-6 persone:single", // leading delimiter in perm
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{6}),
wantPers: utils.Ptr([]string{"single"}),
name: "trailing delimiter in persone",
input: "disp:01/10 perm:3-6 persone:single/",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:single/", // trailing delimiter in persone
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "leading delimiter in persone",
input: "disp:01/10 perm:3-6 persone:/single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:/single", // leading delimiter in persone
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "double delimiter in persone",
input: "disp:01/10 perm:3-6 persone:single//coppia",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single", "coppia"}),
},
{
input: "disp:01/10 perm:3-6 persone:single//coppia", // double delimiter in persone
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single", "coppia"}),
name: "no disp field",
input: "perm:3-6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "perm:3-6 persone:single", // disp missing
wantDisp: nil,
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "no persone field",
input: "disp:01/10 perm:3-6",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: nil,
},
{
input: "disp:01/10 perm:3-6", // persone missing
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: nil,
name: "empty string",
input: "",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: nil,
wantPers: nil,
},
{
input: "disp:01/10 perm: persone:", // perm empty
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: nil,
wantPers: nil,
name: "whitespace in values",
input: "disp: 01/10 perm: 3-6 persone: single ",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "", // completely empty string
wantDisp: nil,
wantPerm: nil,
wantPers: nil,
name: "different order",
input: "persone:single perm:3-6 disp:01/10",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp: 01/10 perm: 3-6 persone: single ",
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "double dash in perm",
input: "disp:01/10 perm:3--6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "persone:single perm:3-6 disp:01/10",
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "tab separators",
input: "disp:01/10\tperm:3-6\tpersone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3--6 persone:single",
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "special chars in persone",
input: "disp:01/10 perm:3-6 persone:single/coppia!",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single", "coppia!"}),
},
{
input: "disp:01/10\tperm:3-6\tpersone:single",
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "duplicate disp - first wins",
input: "disp:01/10 disp:02/11 perm:3-6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 perm:3-6 persone:single/coppia!",
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single", "coppia!"}),
name: "no spaces between fields",
input: "disp:01/10perm:3-6persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(1, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10 disp:02/11 perm:3-6 persone:single",
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())), // Only first disp: is parsed
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "day only in disp",
input: "disp:15 perm:3-6 persone:single",
currConsegna: consegnaOct,
wantDisp: utils.Ptr(buildExpectedDate(15, 10)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
input: "disp:01/10perm:3-6persone:single",
wantDisp: utils.Ptr(fmt.Sprintf("01/10/%d", time.Now().Year())),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
name: "invalid day 31 for February",
input: "disp:31 perm:3-6 persone:single",
currConsegna: consegnaFeb,
wantDisp: utils.Ptr(buildExpectedDate(1, 2)),
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
{
name: "nil currConsegna",
input: "disp:01/10 perm:3-6 persone:single",
currConsegna: nil,
wantDisp: nil,
wantPerm: utils.Ptr([]int{3, 6}),
wantPers: utils.Ptr([]string{"single"}),
},
}
for _, tt := range tests {
disp_time, perm_arr, persone_arr, _ := parser.ParseDettaglio(tt.input)
t.Run(tt.name, func(t *testing.T) {
disp_time, perm_arr, persone_arr, _ := parser.ParseDettaglio(tt.input, tt.currConsegna)
//Handle *string comparison, including nil
if tt.wantDisp != nil {
if disp_time == nil {
t.Errorf("disp_time: got nil, want %v, input: %v", *tt.wantDisp, tt.input)
} else if *disp_time != *tt.wantDisp {
t.Errorf("disp_time: got %v, want %v, input: %v", *disp_time, *tt.wantDisp, tt.input)
// Handle *string comparison, including nil
if tt.wantDisp != nil {
if disp_time == nil {
t.Errorf("disp_time: got nil, want %v", *tt.wantDisp)
} else if *disp_time != *tt.wantDisp {
t.Errorf("disp_time: got %v, want %v", *disp_time, *tt.wantDisp)
}
} else if disp_time != nil {
t.Errorf("disp_time: got %v, want nil", *disp_time)
}
} else if disp_time != nil {
t.Errorf("disp_time: got %v, want nil, input: %v", *disp_time, tt.input)
}
// Handle *[]int comparison, including nil
if tt.wantPerm != nil {
if perm_arr == nil || len(*perm_arr) != len(*tt.wantPerm) {
t.Errorf("perm_arr: got %v, want %v, input: %v", perm_arr, *tt.wantPerm, tt.input)
} else {
for i := range *perm_arr {
if (*perm_arr)[i] != (*tt.wantPerm)[i] {
t.Errorf("perm_arr[%d]: got %v, want %v, input: %v", i, (*perm_arr)[i], (*tt.wantPerm)[i], tt.input)
// Handle *[]int comparison, including nil
if tt.wantPerm != nil {
if perm_arr == nil || len(*perm_arr) != len(*tt.wantPerm) {
t.Errorf("perm_arr: got %v, want %v", perm_arr, *tt.wantPerm)
} else {
for i := range *perm_arr {
if (*perm_arr)[i] != (*tt.wantPerm)[i] {
t.Errorf("perm_arr[%d]: got %v, want %v", i, (*perm_arr)[i], (*tt.wantPerm)[i])
}
}
}
} else if perm_arr != nil && len(*perm_arr) != 0 {
t.Errorf("perm_arr: got %v, want nil", perm_arr)
}
} else if perm_arr != nil && len(*perm_arr) != 0 {
t.Errorf("perm_arr: got %v, want nil, input: %v", perm_arr, tt.input)
}
// Handle *[]string comparison, including nil
if tt.wantPers != nil {
if persone_arr == nil || len(*persone_arr) != len(*tt.wantPers) {
t.Errorf("persone_arr: got %v, want %v, input: %v", persone_arr, *tt.wantPers, tt.input)
} else {
for i := range *persone_arr {
if (*persone_arr)[i] != (*tt.wantPers)[i] {
t.Errorf("persone_arr[%d]: got %v, want %v, input: %v", i, (*persone_arr)[i], (*tt.wantPers)[i], tt.input)
// Handle *[]string comparison, including nil
if tt.wantPers != nil {
if persone_arr == nil || len(*persone_arr) != len(*tt.wantPers) {
t.Errorf("persone_arr: got %v, want %v", persone_arr, *tt.wantPers)
} else {
for i := range *persone_arr {
if (*persone_arr)[i] != (*tt.wantPers)[i] {
t.Errorf("persone_arr[%d]: got %v, want %v", i, (*persone_arr)[i], (*tt.wantPers)[i])
}
}
}
} else if persone_arr != nil && len(*persone_arr) != 0 {
t.Errorf("persone_arr: got %v, want nil", persone_arr)
}
} else if persone_arr != nil && len(*persone_arr) != 0 {
t.Errorf("persone_arr: got %v, want nil, input: %v", persone_arr, tt.input)
}
})
}
}