@@ -0,0 +1,218 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gardomatic.kleiax.de/lib/client"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
func TestPlantAndLocationCanBeEdited(t *testing.T) {
|
||||
var plantInput client.PlantInput
|
||||
var locationInput client.LocationInput
|
||||
assignmentUpdated := false
|
||||
apiHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch {
|
||||
case r.Method == http.MethodPatch && r.URL.Path == "/v1/gardens/3/plants/5":
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &plantInput); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"plant":{"id":5,"garden_id":3,"name":"Neue Rose"}}`))
|
||||
case r.Method == http.MethodPatch && r.URL.Path == "/v1/gardens/3/plants/5/locations/9":
|
||||
assignmentUpdated = true
|
||||
_, _ = w.Write([]byte(`{"plant_location":{"id":9,"plant_id":5,"location_id":6,"quantity":2}}`))
|
||||
case r.Method == http.MethodPatch && r.URL.Path == "/v1/gardens/3/locations/6":
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &locationInput); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"location":{"id":6,"garden_id":3,"name":"Südbeet"}}`))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
app := newAPIBackedTestApplication(t, apiHandler)
|
||||
|
||||
plantForm := url.Values{"name": {"Neue Rose"}, "species_id": {"0"}, "location_id": {"6"}, "assignment_id": {"9"}, "quantity": {"2"}, "status": {"active"}}
|
||||
plantRequest := httptest.NewRequest(http.MethodPost, "/g/3/plants/edit/5", strings.NewReader(plantForm.Encode()))
|
||||
plantRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
plantRequest = taskWebRequest(plantRequest, app.apiClient, httprouter.Params{{Key: "gardenID", Value: "3"}, {Key: "plantID", Value: "5"}})
|
||||
plantResponse := httptest.NewRecorder()
|
||||
app.plantEditPost(plantResponse, plantRequest)
|
||||
if plantResponse.Code != http.StatusSeeOther || !plantInput.ClearSpeciesID || !assignmentUpdated {
|
||||
t.Fatalf("plant edit: status=%d input=%+v assignment=%v body=%s", plantResponse.Code, plantInput, assignmentUpdated, plantResponse.Body.String())
|
||||
}
|
||||
|
||||
locationForm := url.Values{"name": {"Südbeet"}, "parent_id": {"0"}, "area_sqm": {"4,5"}, "return_to": {"/g/3/locations"}}
|
||||
locationRequest := httptest.NewRequest(http.MethodPost, "/g/3/locations/edit/6", strings.NewReader(locationForm.Encode()))
|
||||
locationRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
locationRequest = taskWebRequest(locationRequest, app.apiClient, httprouter.Params{{Key: "gardenID", Value: "3"}, {Key: "locationID", Value: "6"}})
|
||||
locationResponse := httptest.NewRecorder()
|
||||
app.locationEditPost(locationResponse, locationRequest)
|
||||
if locationResponse.Code != http.StatusSeeOther || !locationInput.ClearParentID || locationInput.AreaSQM == nil || *locationInput.AreaSQM != 4.5 {
|
||||
t.Fatalf("location edit: status=%d input=%+v body=%s", locationResponse.Code, locationInput, locationResponse.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGardenAndSpeciesCanBeEdited(t *testing.T) {
|
||||
var gardenInput client.UpdateGardenInput
|
||||
var speciesInput client.SpeciesInput
|
||||
apiHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch {
|
||||
case r.Method == http.MethodPatch && r.URL.Path == "/v1/gardens/3":
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &gardenInput); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"garden":{"id":3,"name":"Neuer Garten"}}`))
|
||||
case r.Method == http.MethodPatch && r.URL.Path == "/v1/gardens/3/species/7":
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &speciesInput); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"species":{"id":7,"garden_id":3,"common_name":"Neue Rose"}}`))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
app := newAPIBackedTestApplication(t, apiHandler)
|
||||
gardenForm := url.Values{"name": {"Neuer Garten"}, "description": {"Südseite"}}
|
||||
gardenRequest := httptest.NewRequest(http.MethodPost, "/gardens/edit/3", strings.NewReader(gardenForm.Encode()))
|
||||
gardenRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
gardenRequest = taskWebRequest(gardenRequest, app.apiClient, httprouter.Params{{Key: "gardenID", Value: "3"}})
|
||||
gardenResponse := httptest.NewRecorder()
|
||||
app.gardenEditPost(gardenResponse, gardenRequest)
|
||||
if gardenResponse.Code != http.StatusSeeOther || gardenInput.Name == nil || *gardenInput.Name != "Neuer Garten" {
|
||||
t.Fatalf("garden edit: status=%d input=%+v", gardenResponse.Code, gardenInput)
|
||||
}
|
||||
speciesForm := url.Values{"common_name": {"Neue Rose"}, "sow_month_from": {"0"}, "sow_month_to": {"0"}}
|
||||
speciesRequest := httptest.NewRequest(http.MethodPost, "/g/3/species/edit/7", strings.NewReader(speciesForm.Encode()))
|
||||
speciesRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
speciesRequest = taskWebRequest(speciesRequest, app.apiClient, httprouter.Params{{Key: "gardenID", Value: "3"}, {Key: "speciesID", Value: "7"}})
|
||||
speciesResponse := httptest.NewRecorder()
|
||||
app.speciesSave(speciesResponse, speciesRequest)
|
||||
if speciesResponse.Code != http.StatusSeeOther || speciesInput.CommonName == nil || *speciesInput.CommonName != "Neue Rose" || !speciesInput.ClearSowRange {
|
||||
t.Fatalf("species edit: status=%d input=%+v", speciesResponse.Code, speciesInput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGardenCanBeDeleted(t *testing.T) {
|
||||
deleted := false
|
||||
apiHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete || r.URL.Path != "/v1/gardens/3" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
deleted = true
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
app := newAPIBackedTestApplication(t, apiHandler)
|
||||
request := httptest.NewRequest(http.MethodPost, "/gardens/delete/3", nil)
|
||||
request = taskWebRequest(request, app.apiClient, httprouter.Params{{Key: "gardenID", Value: "3"}})
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
app.gardenDeletePost(response, request)
|
||||
|
||||
if response.Code != http.StatusSeeOther || response.Header().Get("Location") != "/gardens" || !deleted {
|
||||
t.Fatalf("garden delete: status=%d location=%q deleted=%v body=%s", response.Code, response.Header().Get("Location"), deleted, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskTemplateCanBeCreated(t *testing.T) {
|
||||
var input client.SpeciesTaskTemplateInput
|
||||
apiHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method != http.MethodPost || r.URL.Path != "/v1/gardens/3/species/7/task-templates" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = w.Write([]byte(`{"task_template":{"id":9,"species_id":7,"title":"Schneiden"}}`))
|
||||
})
|
||||
app := newAPIBackedTestApplication(t, apiHandler)
|
||||
form := url.Values{"title": {"Schneiden"}, "trigger_type": {"month_of_year"}, "month_from": {"11"}, "day_from": {"15"}, "duration": {"2"}, "duration_unit": {"week"}, "recurrence": {"monthly"}, "recurrence_interval": {"2"}, "priority": {"5"}, "active": {"true"}}
|
||||
request := httptest.NewRequest(http.MethodPost, "/g/3/task-templates/new?species_id=7", strings.NewReader(form.Encode()))
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
request = taskWebRequest(request, app.apiClient, httprouter.Params{{Key: "gardenID", Value: "3"}})
|
||||
response := httptest.NewRecorder()
|
||||
app.taskTemplateSave(response, request)
|
||||
if response.Code != http.StatusSeeOther || input.TriggerType == nil || *input.TriggerType != "month_of_year" || input.MonthFrom == nil || *input.MonthFrom != 11 || input.DayFrom == nil || *input.DayFrom != 15 || input.Duration == nil || *input.Duration != 2 || input.RecurrenceInterval == nil || *input.RecurrenceInterval != 2 {
|
||||
t.Fatalf("template create: status=%d input=%+v body=%s", response.Code, input, response.Body.String())
|
||||
}
|
||||
if input.TriggerOffset != nil || input.TriggerOffsetUnit != nil {
|
||||
t.Errorf("calendar template unexpectedly sends disabled relative fields: %+v", input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskTemplateDialogShowsMissingNameError(t *testing.T) {
|
||||
apiHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch {
|
||||
case r.Method == http.MethodGet && r.URL.Path == "/v1/gardens/3":
|
||||
_, _ = w.Write([]byte(`{"garden":{"id":3,"name":"Hinterhof","role":"owner"}}`))
|
||||
case r.Method == http.MethodGet && r.URL.Path == "/v1/gardens/3/species/7":
|
||||
_, _ = w.Write([]byte(`{"species":{"id":7,"common_name":"Tomate"}}`))
|
||||
case r.Method == http.MethodGet && r.URL.Path == "/v1/task-priorities":
|
||||
_, _ = w.Write([]byte(`{"priorities":[{"id":1,"name":"Normal","value":0,"active":true}]}`))
|
||||
default:
|
||||
t.Errorf("unexpected API request: %s %s", r.Method, r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
app := newAPIBackedTestApplication(t, apiHandler)
|
||||
form := url.Values{"trigger_type": {"month_of_year"}, "month_from": {"9"}, "day_from": {"1"}, "duration": {"0"}, "duration_unit": {"day"}, "trigger_offset_unit": {"day"}, "recurrence_interval": {"1"}}
|
||||
request := httptest.NewRequest(http.MethodPost, "/g/3/task-templates/new?species_id=7", strings.NewReader(form.Encode()))
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
request.Header.Set("HX-Request", "true")
|
||||
request = taskWebRequest(request, app.apiClient, httprouter.Params{{Key: "gardenID", Value: "3"}})
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
app.taskTemplateSave(response, request)
|
||||
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d, want %d; body: %s", response.Code, http.StatusOK, response.Body.String())
|
||||
}
|
||||
if body := response.Body.String(); !strings.Contains(body, "Ein Name ist erforderlich.") {
|
||||
t.Errorf("missing name validation message: %s", body)
|
||||
}
|
||||
if response.Header().Get("HX-Retarget") != "#task-template-dialog-host" {
|
||||
t.Errorf("HX-Retarget: got %q", response.Header().Get("HX-Retarget"))
|
||||
}
|
||||
|
||||
form.Set("title", "Ausgegeizte Triebe entfernen")
|
||||
form.Set("month_from", "0")
|
||||
request = httptest.NewRequest(http.MethodPost, "/g/3/task-templates/new?species_id=7", strings.NewReader(form.Encode()))
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
request.Header.Set("HX-Request", "true")
|
||||
request = taskWebRequest(request, app.apiClient, httprouter.Params{{Key: "gardenID", Value: "3"}})
|
||||
response = httptest.NewRecorder()
|
||||
|
||||
app.taskTemplateSave(response, request)
|
||||
|
||||
body := response.Body.String()
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("status with filled name: got %d, want %d; body: %s", response.Code, http.StatusOK, body)
|
||||
}
|
||||
if !strings.Contains(body, "value='Ausgegeizte Triebe entfernen'") {
|
||||
t.Errorf("filled name was not preserved: %s", body)
|
||||
}
|
||||
if strings.Contains(body, "Ein Name ist erforderlich.") {
|
||||
t.Errorf("filled name was incorrectly rejected: %s", body)
|
||||
}
|
||||
if !strings.Contains(body, "Tag und Monat für „Ab“ sind erforderlich.") {
|
||||
t.Errorf("actual date validation message is missing: %s", body)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user