From 9ab669868d77f9cb34e3f78816877d226fe4eab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20P=C3=A9rez?= Date: Thu, 23 Oct 2025 03:23:26 +0200 Subject: [PATCH] add more tests cases --- ...madorBackend_202510_datos.csv => meteo.csv | 0 service_a/assets/test_5.csv | 3 + service_a/assets/test_6.csv | 3 + service_a/assets/test_7.csv | 3 + service_a/internal/domains/meteo/domain.go | 35 +++---- service_a/internal/domains/meteo/file.go | 97 +++++++++---------- service_a/internal/domains/meteo/file_test.go | 45 ++++++++- 7 files changed, 114 insertions(+), 72 deletions(-) rename Meteologica_vacante_ProgramadorBackend_202510_datos.csv => meteo.csv (100%) create mode 100644 service_a/assets/test_5.csv create mode 100644 service_a/assets/test_6.csv create mode 100644 service_a/assets/test_7.csv diff --git a/Meteologica_vacante_ProgramadorBackend_202510_datos.csv b/meteo.csv similarity index 100% rename from Meteologica_vacante_ProgramadorBackend_202510_datos.csv rename to meteo.csv diff --git a/service_a/assets/test_5.csv b/service_a/assets/test_5.csv new file mode 100644 index 0000000..f0c6d8c --- /dev/null +++ b/service_a/assets/test_5.csv @@ -0,0 +1,3 @@ +Fecha;Ciudad;Temperatura Máxima (C);Temperatura Mínima (C);Precipitación (mm);Nubosidad (%) +2025/10/12;Madrid;;6,25;0;10 + diff --git a/service_a/assets/test_6.csv b/service_a/assets/test_6.csv new file mode 100644 index 0000000..08e79c0 --- /dev/null +++ b/service_a/assets/test_6.csv @@ -0,0 +1,3 @@ +Fecha;Ciudad;Temperatura Máxima (C);Temperatura Mínima (C);Precipitación (mm);Nubosidad (%) +2025/10/12;;11,55;6,25;0;10 + diff --git a/service_a/assets/test_7.csv b/service_a/assets/test_7.csv new file mode 100644 index 0000000..201f975 --- /dev/null +++ b/service_a/assets/test_7.csv @@ -0,0 +1,3 @@ +Fecha;Ciudad;Temperatura Máxima (C);Temperatura Mínima (C);Precipitación (mm);Nubosidad (%) +;Madrid;11,55;6,25;0;10 + diff --git a/service_a/internal/domains/meteo/domain.go b/service_a/internal/domains/meteo/domain.go index 1215674..9f12793 100644 --- a/service_a/internal/domains/meteo/domain.go +++ b/service_a/internal/domains/meteo/domain.go @@ -29,24 +29,19 @@ type FileStats struct { } var ( - ErrCannotParseFile = errors.New("cannot parse file") - ErrValidateRecord = errors.New("error validating record") - ErrRecordNotValid = errors.New("record not valid") - ErrInvalidDateFormat = errors.New("invalid date format") - ErrReadingCSVHeader = errors.New("error reading CSV header") - ErrReadingCSVRow = errors.New("error reading CSV row") - ErrMissingDateField = errors.New("missing date field") - ErrMissingCityField = errors.New("missing city field") - ErrMissingMaxTempField = errors.New("missing max temp field") - ErrMissingMinTempField = errors.New("missing min temp field") - ErrMissingRainfallField = errors.New("missing rainfall field") - ErrMissingCloudinessField = errors.New("missing cloudiness field") - ErrInvalidMaxTemp = errors.New("invalid max temp") - ErrInvalidMinTemp = errors.New("invalid min temp") - ErrInvalidRainfall = errors.New("invalid rainfall") - ErrInvalidCloudiness = errors.New("invalid cloudiness") - ErrMaxTempOutOfRange = errors.New("max temp out of range (must be <= 60°C)") - ErrMinTempOutOfRange = errors.New("min temp out of range (must be >= -20°C)") - ErrRainfallOutOfRange = errors.New("rainfall out of range (must be 0-500 mm)") - ErrCloudinessOutOfRange = errors.New("cloudiness out of range (must be 0-100%)") + ErrCannotParseFile = errors.New("cannot parse file") + ErrValidateRecord = errors.New("error validating record") + ErrRecordNotValid = errors.New("record not valid") + ErrReadingCSVHeader = errors.New("error reading CSV header") + ErrReadingCSVRow = errors.New("error reading CSV row") + ErrMissingOrInvalidDateField = errors.New("missing or invalid date field") + ErrMissingOrInvalidCityField = errors.New("missing or invalid city field") + ErrMissingOrInvalidMaxTemp = errors.New("missing or invalid max temp field") + ErrMissingOrInvalidMinTemp = errors.New("missing or invalid min temp field") + ErrMissingOrInvalidRainfall = errors.New("missing or invalid rainfall field") + ErrMissingOrInvalidCloudiness = errors.New("missing or invalid cloudiness field") + ErrMaxTempOutOfRange = errors.New("max temp out of range (must be <= 60°C)") + ErrMinTempOutOfRange = errors.New("min temp out of range (must be >= -20°C)") + ErrRainfallOutOfRange = errors.New("rainfall out of range (must be 0-500 mm)") + ErrCloudinessOutOfRange = errors.New("cloudiness out of range (must be 0-100%)") ) diff --git a/service_a/internal/domains/meteo/file.go b/service_a/internal/domains/meteo/file.go index f70aeae..33c317b 100644 --- a/service_a/internal/domains/meteo/file.go +++ b/service_a/internal/domains/meteo/file.go @@ -101,70 +101,67 @@ func (c *CSV) Parse(r io.Reader, fs *FileStats) ([]MeteoData, []RejectedMeteoDat func normalize(record H) (*MeteoData, error) { meteoData := &MeteoData{} - if dateStr, ok := record["Fecha"].(string); ok { - t, err := time.Parse("2006/01/02", dateStr) - if err != nil { - return nil, fmt.Errorf("%w: %v", ErrInvalidDateFormat, err) - } - meteoData.Timestamp = t - } else { - return nil, ErrMissingDateField + var err error + + meteoData.Timestamp, err = parseDate(record, "Fecha", ErrMissingOrInvalidDateField) + if err != nil { + return nil, err } - if location, ok := record["Ciudad"].(string); ok { - meteoData.Location = location - } else { - return nil, ErrMissingCityField + meteoData.Location, err = parseString(record, "Ciudad", ErrMissingOrInvalidCityField) + if err != nil { + return nil, err } - if maxTempStr, ok := record["Temperatura Máxima (C)"].(string); ok { - maxTemp, err := parseFloat(maxTempStr) - if err != nil { - return nil, fmt.Errorf("%w: %v", ErrInvalidMaxTemp, err) - } - meteoData.MaxTemp = maxTemp - } else { - return nil, ErrMissingMaxTempField + meteoData.MaxTemp, err = parseFloatField(record, "Temperatura Máxima (C)", ErrMissingOrInvalidMaxTemp) + if err != nil { + return nil, err } - if minTempStr, ok := record["Temperatura Mínima (C)"].(string); ok { - minTemp, err := parseFloat(minTempStr) - if err != nil { - return nil, fmt.Errorf("%w: %v", ErrInvalidMinTemp, err) - } - meteoData.MinTemp = minTemp - } else { - return nil, ErrMissingMinTempField + meteoData.MinTemp, err = parseFloatField(record, "Temperatura Mínima (C)", ErrMissingOrInvalidMinTemp) + if err != nil { + return nil, err } - if rainfallStr, ok := record["Precipitación (mm)"].(string); ok { - rainfall, err := parseFloat(rainfallStr) - if err != nil { - return nil, fmt.Errorf("%w: %v", ErrInvalidRainfall, err) - } - meteoData.Rainfall = rainfall - } else { - return nil, ErrMissingRainfallField + meteoData.Rainfall, err = parseFloatField(record, "Precipitación (mm)", ErrMissingOrInvalidRainfall) + if err != nil { + return nil, err } - if cloudinessStr, ok := record["Nubosidad (%)"].(string); ok { - cloudiness, err := parseFloat(cloudinessStr) - if err != nil { - return nil, fmt.Errorf("%w: %v", ErrInvalidCloudiness, err) - } - meteoData.Cloudiness = cloudiness - } else { - return nil, ErrMissingCloudinessField + meteoData.Cloudiness, err = parseFloatField(record, "Nubosidad (%)", ErrMissingOrInvalidCloudiness) + if err != nil { + return nil, err } return meteoData, nil } -func parseFloat(s string) (float32, error) { - s = strings.Replace(s, ",", ".", 1) - f, err := strconv.ParseFloat(s, 32) - if err != nil { - return 0, err +func parseDate(record H, key string, errMissing error) (time.Time, error) { + if str, ok := record[key].(string); ok && str != "" { + t, err := time.Parse("2006/01/02", str) + if err != nil { + return time.Time{}, errMissing + } + return t, nil } - return float32(f), nil + return time.Time{}, errMissing +} + +func parseString(record H, key string, errMissing error) (string, error) { + if str, ok := record[key].(string); ok && str != "" { + return str, nil + } + return "", errMissing +} + +func parseFloatField(record H, key string, errMissing error) (float32, error) { + if str, ok := record[key].(string); ok && str != "" { + str = strings.Replace(str, ",", ".", 1) + f, err := strconv.ParseFloat(str, 32) + if err != nil { + return 0, errMissing + } + return float32(f), nil + } + return 0, errMissing } diff --git a/service_a/internal/domains/meteo/file_test.go b/service_a/internal/domains/meteo/file_test.go index 3a33c76..e754907 100644 --- a/service_a/internal/domains/meteo/file_test.go +++ b/service_a/internal/domains/meteo/file_test.go @@ -44,7 +44,6 @@ func Test_CSV_ParseFile(t *testing.T) { expectedRejected: 0, validateInserted: func(t *testing.T, inserted []meteo.MeteoData) { assert.Equal(t, 1, len(inserted)) - // TrimLeadingSpace should handle the spaces before Madrid assert.Equal(t, "Madrid", inserted[0].Location) }, validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) { @@ -61,7 +60,7 @@ func Test_CSV_ParseFile(t *testing.T) { }, validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) { assert.Equal(t, 1, len(rejected)) - assert.Contains(t, rejected[0].Reason, "missing city field") + assert.Contains(t, rejected[0].Reason, "missing or invalid city field") assert.Equal(t, "2025/10/12;11,55;6,25;0;10", rejected[0].RowValue) }, }, @@ -77,6 +76,48 @@ func Test_CSV_ParseFile(t *testing.T) { assert.Empty(t, rejected) }, }, + { + name: "missing max temp field value", + filePath: "./../../../assets/test_5.csv", + expectedInserted: 0, + expectedRejected: 1, + validateInserted: func(t *testing.T, inserted []meteo.MeteoData) { + assert.Empty(t, inserted) + }, + validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) { + assert.Equal(t, 1, len(rejected)) + assert.Contains(t, rejected[0].Reason, "missing or invalid max temp field") + assert.Equal(t, "2025/10/12;Madrid;;6,25;0;10", rejected[0].RowValue) + }, + }, + { + name: "missing city field value", + filePath: "./../../../assets/test_6.csv", + expectedInserted: 0, + expectedRejected: 1, + validateInserted: func(t *testing.T, inserted []meteo.MeteoData) { + assert.Empty(t, inserted) + }, + validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) { + assert.Equal(t, 1, len(rejected)) + assert.Contains(t, rejected[0].Reason, "missing or invalid city field") + assert.Equal(t, "2025/10/12;;11,55;6,25;0;10", rejected[0].RowValue) + }, + }, + { + name: "missing date field value", + filePath: "./../../../assets/test_7.csv", + expectedInserted: 0, + expectedRejected: 1, + validateInserted: func(t *testing.T, inserted []meteo.MeteoData) { + assert.Empty(t, inserted) + }, + validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) { + assert.Equal(t, 1, len(rejected)) + assert.Contains(t, rejected[0].Reason, "missing or invalid date field") + assert.Equal(t, ";Madrid;11,55;6,25;0;10", rejected[0].RowValue) + }, + }, } for _, tt := range tests {