Skip to content

Commit

Permalink
Move NotificationClientID to Garden
Browse files Browse the repository at this point in the history
- Also improve tests by using VHS for Pushover client
  • Loading branch information
calvinmclean committed Jul 17, 2024
1 parent afa3096 commit 75ac031
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 67 deletions.
1 change: 0 additions & 1 deletion deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# - run-local: run required services + extras like Grafana and Prometheus
# - demo: run everything, including an instance of garden-app and garden-controller

version: "3.9"
services:
grafana:
image: "grafana/grafana:latest"
Expand Down
16 changes: 14 additions & 2 deletions garden-app/pkg/garden.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Garden struct {
EndDate *time.Time `json:"end_date,omitempty" yaml:"end_date,omitempty"`
LightSchedule *LightSchedule `json:"light_schedule,omitempty" yaml:"light_schedule,omitempty"`
TemperatureHumiditySensor *bool `json:"temperature_humidity_sensor,omitempty" yaml:"temperature_humidity_sensor,omitempty"`
NotificationClientID *string `json:"notification_client_id,omitempty" yaml:"notification_client_id,omitempty"`
}

func (g *Garden) GetID() string {
Expand All @@ -41,6 +42,14 @@ func (g *Garden) String() string {
return fmt.Sprintf("%+v", *g)
}

func (g *Garden) GetNotificationClientID() string {
if g.NotificationClientID == nil {
return ""
}

return *g.NotificationClientID
}

// GardenHealth holds information about the Garden controller's health status
type GardenHealth struct {
Status HealthStatus `json:"status,omitempty"`
Expand Down Expand Up @@ -117,15 +126,18 @@ func (g *Garden) Patch(newGarden *Garden) *babyapi.ErrResponse {

// If both Duration and StartTime are empty, remove the schedule
if newGarden.LightSchedule.Duration == nil &&
newGarden.LightSchedule.StartTime == nil &&
newGarden.LightSchedule.NotificationClientID == nil {
newGarden.LightSchedule.StartTime == nil {
g.LightSchedule = nil
}
}
if newGarden.TemperatureHumiditySensor != nil {
g.TemperatureHumiditySensor = newGarden.TemperatureHumiditySensor
}

if newGarden.NotificationClientID != nil {
g.NotificationClientID = newGarden.NotificationClientID
}

return nil
}

Expand Down
18 changes: 3 additions & 15 deletions garden-app/pkg/light_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,16 @@ func (l *LightState) unmarshal(data []byte) error {
// LightSchedule allows the user to control when the Garden light is turned on and off
// "Time" should be in the format of LightTimeFormat constant ("15:04:05-07:00")
type LightSchedule struct {
Duration *Duration `json:"duration" yaml:"duration"`
StartTime *StartTime `json:"start_time" yaml:"start_time"`
AdhocOnTime *time.Time `json:"adhoc_on_time,omitempty" yaml:"adhoc_on_time,omitempty"`
NotificationClientID *string `json:"notification_client_id,omitempty" yaml:"notification_client_id,omitempty"`
Duration *Duration `json:"duration" yaml:"duration"`
StartTime *StartTime `json:"start_time" yaml:"start_time"`
AdhocOnTime *time.Time `json:"adhoc_on_time,omitempty" yaml:"adhoc_on_time,omitempty"`
}

// String...
func (ls *LightSchedule) String() string {
return fmt.Sprintf("%+v", *ls)
}

func (ls *LightSchedule) GetNotificationClientID() string {
if ls.NotificationClientID == nil {
return ""
}

return *ls.NotificationClientID
}

// Patch allows modifying the struct in-place with values from a different instance
func (ls *LightSchedule) Patch(new *LightSchedule) {
if new.Duration != nil {
Expand All @@ -96,7 +87,4 @@ func (ls *LightSchedule) Patch(new *LightSchedule) {
if new.AdhocOnTime == nil {
ls.AdhocOnTime = nil
}
if new.NotificationClientID != nil {
ls.NotificationClientID = new.NotificationClientID
}
}
2 changes: 1 addition & 1 deletion garden-app/pkg/storage/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (

"github.com/calvinmclean/babyapi"
"github.com/calvinmclean/babyapi/storage/kv"
"github.com/mitchellh/mapstructure"
"github.com/tarmac-project/hord"
"github.com/tarmac-project/hord/drivers/hashmap"
"github.com/tarmac-project/hord/drivers/redis"
"github.com/mitchellh/mapstructure"
)

// Config is used to identify and configure a storage client
Expand Down
14 changes: 7 additions & 7 deletions garden-app/server/garden.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,15 @@ func (api *GardensAPI) onCreateOrUpdate(_ http.ResponseWriter, r *http.Request,
}
}

if garden.LightSchedule != nil {
// Validate NotificationClient exists
if garden.LightSchedule.NotificationClientID != nil {
apiErr := checkNotificationClientExists(r.Context(), api.storageClient, *garden.LightSchedule.NotificationClientID)
if apiErr != nil {
return apiErr
}
// Validate NotificationClient exists
if garden.NotificationClientID != nil {
apiErr := checkNotificationClientExists(r.Context(), api.storageClient, *garden.NotificationClientID)
if apiErr != nil {
return apiErr
}
}

if garden.LightSchedule != nil {
// Update the light schedule for the Garden (if it exists)
logger.Info("updating/resetting LightSchedule for Garden")
if err := api.worker.ResetLightSchedule(garden); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions garden-app/server/garden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,16 +437,16 @@ func TestUpdateGarden(t *testing.T) {
"AddNotificationClientIDErrorNotFound",
createExampleGarden(),
nil,
`{"light_schedule":{"notification_client_id":"NOTIFICATION_CLIENT_ID"}}`,
`{"notification_client_id":"NOTIFICATION_CLIENT_ID"}`,
`{"status":"Invalid request.","error":"error getting NotificationClient with ID \\"NOTIFICATION_CLIENT_ID\\": resource not found"}`,
http.StatusBadRequest,
},
{
"AddNotificationClientIDSuccess",
createExampleGarden(),
nil,
`{"light_schedule":{"notification_client_id":"c5cvhpcbcv45e8bp16dg"}}`,
`{"name":"test-garden","topic_prefix":"test-garden","id":"[0-9a-v]{20}","max_zones":2,"created_at":"\d{4}-\d{2}-\d\dT\d\d:\d\d:\d\d\.\d+(-07:00|Z)","light_schedule":{"duration":"15h0m0s","start_time":"22:00:01-07:00","notification_client_id":"c5cvhpcbcv45e8bp16dg"},"next_light_action":{"time":"0000-12-31T17:00:00-07:00","state":"OFF"},"health":{"status":"UP","details":"last contact from Garden was \d+(s|ms) ago","last_contact":"\d{4}-\d{2}-\d\dT\d\d:\d\d:\d\d\.\d+(-07:00|Z)"},"num_zones":1,"links":\[{"rel":"self","href":"/gardens/[0-9a-v]{20}"},{"rel":"zones","href":"/gardens/c5cvhpcbcv45e8bp16dg/zones"},{"rel":"action","href":"/gardens/[0-9a-v]{20}/action"}\]}`,
`{"notification_client_id":"c5cvhpcbcv45e8bp16dg"}`,
`{"name":"test-garden","topic_prefix":"test-garden","id":"[0-9a-v]{20}","max_zones":2,"created_at":"\d{4}-\d{2}-\d\dT\d\d:\d\d:\d\d\.\d+(-07:00|Z)","light_schedule":{"duration":"15h0m0s","start_time":"22:00:01-07:00"},"notification_client_id":"c5cvhpcbcv45e8bp16dg","next_light_action":{"time":"0000-12-31T17:00:00-07:00","state":"OFF"},"health":{"status":"UP","details":"last contact from Garden was \d+(s|ms) ago","last_contact":"\d{4}-\d{2}-\d\dT\d\d:\d\d:\d\d\.\d+(-07:00|Z)"},"num_zones":1,"links":\[{"rel":"self","href":"/gardens/[0-9a-v]{20}"},{"rel":"zones","href":"/gardens/c5cvhpcbcv45e8bp16dg/zones"},{"rel":"action","href":"/gardens/[0-9a-v]{20}/action"}\]}`,
http.StatusOK,
},
{
Expand Down
12 changes: 5 additions & 7 deletions garden-app/server/templates/garden_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,15 @@ <h3 class="uk-modal-title">{{ if .Name }}{{ .Name }}{{ else }}Create Garden{{ en

<div class="uk-margin">
<label class="uk-form-label" for="notification-client-select">Notification Client</label>
<select id="notification-client-select" class="uk-select" name="LightSchedule.NotificationClientID">
<select id="notification-client-select" class="uk-select" name="NotificationClientID">
{{ $noClientSelected := true }}
{{ if ne .LightSchedule nil }}
$noClientSelected = eq .LightSchedule.NotificationClientID nil
{{ end }}
$noClientSelected = eq .NotificationClientID nil
<option disabled {{ if $noClientSelected }}selected{{ end }}>Notification Client</option>

{{ $ls := .LightSchedule }}
{{ $g := . }}
{{ range $i, $nc := .NotificationClients }}
{{/* check if $ls is nil before calling CompareNotificationClientID */}}
{{ $selected := and $ls (CompareNotificationClientID $nc.GetID $ls) }}
{{/* check if $g is nil before calling CompareNotificationClientID */}}
{{ $selected := and $g (CompareNotificationClientID $nc.GetID $g) }}
<option value="{{ $nc.GetID }}" {{ if $selected }}selected{{ end }}>{{ $nc.Name }}</option>
{{ end }}
</select>
Expand Down
80 changes: 80 additions & 0 deletions garden-app/server/testdata/fixtures/pushover_fail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
version: 2
interactions:
- id: 0
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
content_length: 131
transfer_encoding: []
trailer: {}
host: api.pushover.net
remote_addr: ""
request_uri: ""
body: message=watered+for+6s&priority=0&title=+finished+watering&token=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&user=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
form:
message:
- watered for 6s
priority:
- "0"
title:
- ' finished watering'
token:
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
user:
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
headers:
Content-Type:
- application/x-www-form-urlencoded
url: https://api.pushover.net/1/messages.json
method: POST
response:
proto: HTTP/2.0
proto_major: 2
proto_minor: 0
transfer_encoding: []
trailer: {}
content_length: -1
uncompressed: false
body: '{"token":"invalid","errors":["application token is invalid, see https://pushover.net/api"],"status":0,"request":"85158a2e-3f89-456b-9262-d14915ad76bc"}'
headers:
Access-Control-Allow-Headers:
- X-Requested-With, X-Prototype-Version, Origin, Accept, Content-Type, X-CSRF-Token, X-Pushover-App, Authorization
Access-Control-Allow-Methods:
- POST, OPTIONS
Access-Control-Allow-Origin:
- '*'
Access-Control-Max-Age:
- "1728000"
Cache-Control:
- no-cache
Cf-Cache-Status:
- DYNAMIC
Cf-Ray:
- 8a4749a09ad07c2d-LAX
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 17 Jul 2024 03:53:54 GMT
Referrer-Policy:
- strict-origin-when-cross-origin
Server:
- cloudflare
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-Request-Id:
- 85158a2e-3f89-456b-9262-d14915ad76bc
X-Runtime:
- "0.007297"
X-Xss-Protection:
- 1; mode=block
status: 400 Bad Request
code: 400
duration: 308.7185ms
93 changes: 93 additions & 0 deletions garden-app/server/testdata/fixtures/pushover_success.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
version: 2
interactions:
- id: 0
request:
proto: HTTP/1.1
proto_major: 1
proto_minor: 1
content_length: 131
transfer_encoding: []
trailer: {}
host: api.pushover.net
remote_addr: ""
request_uri: ""
body: message=watered+for+6s&priority=0&title=+finished+watering&token=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&user=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
form:
message:
- watered for 6s
priority:
- "0"
title:
- " finished watering"
token:
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
user:
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
headers:
Content-Type:
- application/x-www-form-urlencoded
url: https://api.pushover.net/1/messages.json
method: POST
response:
proto: HTTP/2.0
proto_major: 2
proto_minor: 0
transfer_encoding: []
trailer: {}
content_length: -1
uncompressed: true
body: '{"status":1,"request":"2e1d184d-da34-414c-ae3c-05022dc4a2f9"}'
headers:
Access-Control-Allow-Headers:
- X-Requested-With, X-Prototype-Version, Origin, Accept, Content-Type, X-CSRF-Token, X-Pushover-App, Authorization
Access-Control-Allow-Methods:
- POST, OPTIONS
Access-Control-Allow-Origin:
- "*"
Access-Control-Max-Age:
- "1728000"
Cache-Control:
- max-age=0, private, must-revalidate
Cf-Cache-Status:
- DYNAMIC
Cf-Ray:
- 8a47468bac122b54-LAX
Content-Type:
- application/json; charset=utf-8
Date:
- Wed, 17 Jul 2024 03:51:48 GMT
Etag:
- W/"fbf804824a9232da76ba211e79a0cd4a"
Referrer-Policy:
- strict-origin-when-cross-origin
Server:
- cloudflare
Strict-Transport-Security:
- max-age=31536000
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- SAMEORIGIN
- DENY
X-Limit-App-Limit:
- "10000"
X-Limit-App-Remaining:
- "9937"
X-Limit-App-Reset:
- "1722488400"
X-Permitted-Cross-Domain-Policies:
- none
X-Po-H:
- a
X-Request-Id:
- 2e1d184d-da34-414c-ae3c-05022dc4a2f9
X-Runtime:
- "0.056145"
X-Xss-Protection:
- 1; mode=block
status: 200 OK
code: 200
duration: 416.0575ms
Loading

0 comments on commit 75ac031

Please sign in to comment.