Skip to content

Commit

Permalink
Refactor for testability
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchellrj committed Aug 10, 2018
1 parent 0a5025d commit 7b8b0a5
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 22 deletions.
5 changes: 2 additions & 3 deletions groups.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package main

import (
hue "github.com/collinux/gohue"
log "github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus"
)

type groupCollector struct {
bridge *hue.Bridge
bridge Bridge
groupBrightness *prometheus.GaugeVec
groupHue *prometheus.GaugeVec
groupSaturation *prometheus.GaugeVec
Expand All @@ -21,7 +20,7 @@ var variableGroupLabelNames = []string{
}

// NewGroupCollector Create a new Hue collector for groups
func NewGroupCollector(namespace string, bridge *hue.Bridge) prometheus.Collector {
func NewGroupCollector(namespace string, bridge Bridge) prometheus.Collector {
c := groupCollector{
bridge: bridge,
groupBrightness: prometheus.NewGaugeVec(
Expand Down
38 changes: 38 additions & 0 deletions groups_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
hue "github.com/collinux/gohue"
"github.com/mitchellrj/hue_exporter/test"
"github.com/prometheus/client_golang/prometheus"
"testing"
)

func TestGroupCollector(t *testing.T) {
bridge := test.NewStubBridge().WithGroups([]hue.Group{
hue.Group{
Action: hue.Action{
Bri: 256,
Hue: 100,
Sat: 80,
},
State: struct {
AllOn bool `json:"all_on"`
AnyOn bool `json:"any_on"`
}{
AllOn: true,
AnyOn: true,
},
Type: "Room",
Name: "Living room",
},
})

metrics := make(chan prometheus.Metric, 5)
collector := NewGroupCollector("test_hue", bridge)
collector.Collect(metrics)
close(metrics)

for metric := range metrics {
t.Logf("%v\n", metric)
}
}
5 changes: 2 additions & 3 deletions lights.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package main

import (
hue "github.com/collinux/gohue"
log "github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus"
)

type lightCollector struct {
bridge *hue.Bridge
bridge Bridge
lightBrightness *prometheus.GaugeVec
lightHue *prometheus.GaugeVec
lightSaturation *prometheus.GaugeVec
Expand All @@ -26,7 +25,7 @@ var variableLightLabelNames = []string{
}

// NewLightCollector Create a new Hue collector for lights
func NewLightCollector(namespace string, bridge *hue.Bridge) prometheus.Collector {
func NewLightCollector(namespace string, bridge Bridge) prometheus.Collector {
c := lightCollector{
bridge: bridge,
lightBrightness: prometheus.NewGaugeVec(
Expand Down
48 changes: 35 additions & 13 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,36 +34,45 @@ type Config struct {
APIKey string `yaml:"api_key"`
SensorConfig struct {
IgnoreTypes []string `yaml:"ignore_types"`
MatchNames bool `yaml:"match_names"`
} `yaml:"sensors"`
}

func runServer() {

raw, err := ioutil.ReadFile(*config)
if err != nil {
log.Fatalf("Error reading config file: %v\n", err)
}
// Bridge is an interface for the bridge struct from Collinux/gohue to allow stubbing in tests
type Bridge interface {
Login(string) error
GetAllSensors() ([]hue.Sensor, error)
GetAllLights() ([]hue.Light, error)
GetAllGroups() ([]hue.Group, error)
}

var cfg Config
err = yaml.Unmarshal(raw, &cfg)
func readConfig(raw []byte, cfg *Config) {
err := yaml.Unmarshal(raw, cfg)
if err != nil {
log.Fatalf("Error parsing config file: %v\n", err)
}
}

bridge, err := hue.NewBridge(cfg.IPAddr)
func newBridge(ipAddr string) Bridge {
bridge, err := hue.NewBridge(ipAddr)
if err != nil {
log.Fatalf("Error connecting to Hue bridge at %v: %v\n", cfg.IPAddr, err)
log.Fatalf("Error connecting to Hue bridge at %v: %v\n", ipAddr, err)
}
return bridge
}

err = bridge.Login(cfg.APIKey)
func setupPrometheus(bridge Bridge, cfg *Config) {
err := bridge.Login((*cfg).APIKey)
if err != nil {
log.Fatalf("Error authenticating with Hue bridge at %v: %v\n", cfg.IPAddr, err)
log.Fatalf("Error authenticating with Hue bridge at %v: %v\n", (*cfg).IPAddr, err)
}

prometheus.MustRegister(NewGroupCollector(namespace, bridge))
prometheus.MustRegister(NewLightCollector(namespace, bridge))
prometheus.MustRegister(NewSensorCollector(namespace, bridge, cfg.SensorConfig.IgnoreTypes))
prometheus.MustRegister(NewSensorCollector(namespace, bridge, (*cfg).SensorConfig.IgnoreTypes, (*cfg).SensorConfig.MatchNames))
}

func listen() {
http.Handle("/metrics", promhttp.Handler())
srv := &http.Server{
Addr: (*addr).String(),
Expand All @@ -73,6 +82,19 @@ func runServer() {
log.Fatal(srv.ListenAndServe())
}

func runServer() {
var cfg Config

raw, err := ioutil.ReadFile(*config)
if err != nil {
log.Fatalf("Error reading config file: %v\n", err)
}
readConfig(raw, &cfg)
bridge := newBridge(cfg.IPAddr)
setupPrometheus(bridge, &cfg)
listen()
}

func main() {
command := kingpin.MustParse(app.Parse(os.Args[1:]))
if *version {
Expand Down
4 changes: 2 additions & 2 deletions sensors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

type sensorCollector struct {
bridge *hue.Bridge
bridge Bridge
ignoreTypes []string
matchNames bool
sensorValue *prometheus.GaugeVec
Expand Down Expand Up @@ -38,7 +38,7 @@ func contains(a []string, x string) bool {
}

// NewSensorCollector Create a new Hue collector for sensors
func NewSensorCollector(namespace string, bridge *hue.Bridge, ignoreTypes []string, matchNames bool) prometheus.Collector {
func NewSensorCollector(namespace string, bridge Bridge, ignoreTypes []string, matchNames bool) prometheus.Collector {
c := sensorCollector{
bridge: bridge,
ignoreTypes: ignoreTypes,
Expand Down
77 changes: 77 additions & 0 deletions test/bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package test

import (
"context"
"errors"
hue "github.com/collinux/gohue"
)

type APIFailure int

const (
LoginFailure APIFailure = iota
GetGroupsFailure
GetLightsFailure
GetSensorsFailure
)

type stubHueBridge struct {
ctx context.Context
lights []hue.Light
groups []hue.Group
sensors []hue.Sensor
}

func NewStubBridge() *stubHueBridge {
return &stubHueBridge{
ctx: context.Background(),
}
}

func (s *stubHueBridge) WithFailure(fail APIFailure) *stubHueBridge {
s.ctx = context.WithValue(s.ctx, fail, true)
return s
}

func (s *stubHueBridge) WithGroups(groups []hue.Group) *stubHueBridge {
s.groups = groups
return s
}

func (s *stubHueBridge) WithLights(lights []hue.Light) *stubHueBridge {
s.lights = lights
return s
}

func (s *stubHueBridge) WithSensors(sensors []hue.Sensor) *stubHueBridge {
s.sensors = sensors
return s
}

func (s *stubHueBridge) Login(apiKey string) error {
if val, ok := s.ctx.Value(LoginFailure).(bool); ok && val {
return errors.New("Deliberate login failure")
}
return nil
}

func (s *stubHueBridge) GetAllGroups() ([]hue.Group, error) {
if val, ok := s.ctx.Value(GetGroupsFailure).(bool); ok && val {
return []hue.Group{}, errors.New("Deliberate get groups failure")
}
return s.groups, nil
}

func (s *stubHueBridge) GetAllLights() ([]hue.Light, error) {
if val, ok := s.ctx.Value(GetLightsFailure).(bool); ok && val {
return []hue.Light{}, errors.New("Deliberate get lights failure")
}
return s.lights, nil
}

func (s *stubHueBridge) GetAllSensors() ([]hue.Sensor, error) {
if val, ok := s.ctx.Value(GetSensorsFailure).(bool); ok && val {
return []hue.Sensor{}, errors.New("Deliberate get sensors failure")
}
return s.sensors, nil
}
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package main

const VERSION = "0.1.0"
const VERSION = "0.2.0"

0 comments on commit 7b8b0a5

Please sign in to comment.