func TestLogs(t *testing.T) { a := New(t) store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) h := &handler{ applications: store, } m := &handlerManager{handler: h} msg := &pb.DryDownlinkMessage{ Fields: `{ "foo": [ 1, 2, 3 ] }`, App: &pb.Application{ Encoder: ` function Encoder (fields) { console.log("foo", 1, "bar", new Date(0)) console.log(1, { baz: 10, baa: "foo", bal: { "bar": 10 }}) return fields.foo }`, }, } res, err := m.DryDownlink(context.TODO(), msg) a.So(err, ShouldBeNil) a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ &pb.LogEntry{ Function: "encoder", Fields: []string{`"foo"`, "1", `"bar"`, `"1970-01-01T00:00:00.000Z"`}, }, &pb.LogEntry{ Function: "encoder", Fields: []string{"1", `{"baa":"foo","bal":{"bar":10},"baz":10}`}, }, }) }
func TestConvertFieldsDownNoPort(t *testing.T) { a := New(t) appID := "AppID-1" h := &handler{ applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-down"), } // Case1: No Encoder ttnDown, appDown := buildConversionDownlink() err := h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) a.So(err, ShouldBeNil) a.So(appDown.PayloadRaw, ShouldBeEmpty) // Case2: Normal flow with Encoder h.applications.Set(&application.Application{ AppID: appID, // Encoder takes JSON fields as argument and return the payload as []byte Encoder: `function Encoder (payload){ return [ 1, 2, 3, 4, 5, 6, 7 ] }`, }) defer func() { h.applications.Delete(appID) }() ttnDown, appDown = buildConversionDownlink() err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) a.So(err, ShouldBeNil) a.So(appDown.PayloadRaw, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) }
// NewRedisHandler creates a new Redis-backed Handler func NewRedisHandler(client *redis.Client, ttnBrokerID string) Handler { return &handler{ devices: device.NewRedisDeviceStore(client, "handler"), applications: application.NewRedisApplicationStore(client, "handler"), ttnBrokerID: ttnBrokerID, } }
func TestDryDownlinkPayload(t *testing.T) { a := New(t) store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) h := &handler{ applications: store, } m := &handlerManager{handler: h} msg := &pb.DryDownlinkMessage{ Payload: []byte{0x1, 0x2, 0x3}, App: &pb.Application{ Encoder: `function (fields) { return fields.foo }`, }, } res, err := m.DryDownlink(context.TODO(), msg) a.So(err, ShouldBeNil) a.So(res.Payload, ShouldResemble, []byte{0x1, 0x2, 0x3}) a.So(res.Logs, ShouldResemble, []*pb.LogEntry(nil)) // make sure no calls to app store were made a.So(store.Count("list"), ShouldEqual, 0) a.So(store.Count("get"), ShouldEqual, 0) a.So(store.Count("set"), ShouldEqual, 0) a.So(store.Count("delete"), ShouldEqual, 0) }
func TestDryUplinkEmptyApp(t *testing.T) { a := New(t) store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-uplink")) h := &handler{ applications: store, } m := &handlerManager{handler: h} dryUplinkMessage := &pb.DryUplinkMessage{ Payload: []byte{11, 22, 33}, } res, err := m.DryUplink(context.TODO(), dryUplinkMessage) a.So(err, ShouldBeNil) a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload) a.So(res.Fields, ShouldEqual, "") a.So(res.Valid, ShouldBeTrue) // make sure no calls to app store were made a.So(store.Count("list"), ShouldEqual, 0) a.So(store.Count("get"), ShouldEqual, 0) a.So(store.Count("set"), ShouldEqual, 0) a.So(store.Count("delete"), ShouldEqual, 0) }
func TestConvertFieldsUp(t *testing.T) { a := New(t) appID := "AppID-1" h := &handler{ applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-up"), } // No functions ttnUp, appUp := buildConversionUplink(appID) err := h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.PayloadFields, ShouldBeEmpty) // Normal flow app := &application.Application{ AppID: appID, Decoder: `function Decoder (data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, } a.So(h.applications.Set(app), ShouldBeNil) defer func() { h.applications.Delete(appID) }() ttnUp, appUp = buildConversionUplink(appID) err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.PayloadFields, ShouldResemble, map[string]interface{}{ "temperature": 21.6, }) // Invalidate data app.StartUpdate() app.Validator = `function Validator (data) { return false; }` h.applications.Set(app) ttnUp, appUp = buildConversionUplink(appID) err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldNotBeNil) a.So(appUp.PayloadFields, ShouldBeEmpty) // Function error app.StartUpdate() app.Validator = `function Validator (data) { throw "expected"; }` h.applications.Set(app) ttnUp, appUp = buildConversionUplink(appID) err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.PayloadFields, ShouldBeEmpty) }
func TestDryUplinkFields(t *testing.T) { a := New(t) store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-uplink")) h := &handler{ applications: store, } m := &handlerManager{handler: h} dryUplinkMessage := &pb.DryUplinkMessage{ Payload: []byte{11, 22, 33}, App: &pb.Application{ AppId: "DryUplinkFields", Decoder: `function Decoder (bytes) { console.log("hi", 11) return { length: bytes.length }}`, Converter: `function Converter (obj) { console.log("foo") return obj }`, Validator: `function Validator (bytes) { return true; }`, }, } res, err := m.DryUplink(context.TODO(), dryUplinkMessage) a.So(err, ShouldBeNil) a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload) a.So(res.Fields, ShouldEqual, `{"length":3}`) a.So(res.Valid, ShouldBeTrue) a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ &pb.LogEntry{ Function: "decoder", Fields: []string{`"hi"`, "11"}, }, &pb.LogEntry{ Function: "converter", Fields: []string{`"foo"`}, }, }) // make sure no calls to app store were made a.So(store.Count("list"), ShouldEqual, 0) a.So(store.Count("get"), ShouldEqual, 0) a.So(store.Count("set"), ShouldEqual, 0) a.So(store.Count("delete"), ShouldEqual, 0) }
func TestDryDownlinkEmptyApp(t *testing.T) { a := New(t) store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) h := &handler{ applications: store, } m := &handlerManager{handler: h} msg := &pb.DryDownlinkMessage{ Fields: `{ "foo": [ 1, 2, 3 ] }`, } _, err := m.DryDownlink(context.TODO(), msg) a.So(err, ShouldNotBeNil) // make sure no calls to app store were made a.So(store.Count("list"), ShouldEqual, 0) a.So(store.Count("get"), ShouldEqual, 0) a.So(store.Count("set"), ShouldEqual, 0) a.So(store.Count("delete"), ShouldEqual, 0) }
func TestDryDownlinkFields(t *testing.T) { a := New(t) store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) h := &handler{ applications: store, } m := &handlerManager{handler: h} msg := &pb.DryDownlinkMessage{ Fields: `{ "foo": [ 1, 2, 3 ] }`, App: &pb.Application{ Encoder: ` function Encoder (fields) { console.log("hello", { foo: 33 }) return fields.foo }`, }, } res, err := m.DryDownlink(context.TODO(), msg) a.So(err, ShouldBeNil) a.So(res.Payload, ShouldResemble, []byte{1, 2, 3}) a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ &pb.LogEntry{ Function: "encoder", Fields: []string{`"hello"`, `{"foo":33}`}, }, }) // make sure no calls to app store were made a.So(store.Count("list"), ShouldEqual, 0) a.So(store.Count("get"), ShouldEqual, 0) a.So(store.Count("set"), ShouldEqual, 0) a.So(store.Count("delete"), ShouldEqual, 0) }
func TestHandleUplink(t *testing.T) { a := New(t) var err error var wg WaitGroup appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) appID := "appid" devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) devID := "devid" h := &handler{ Component: &component.Component{Ctx: GetLogger(t, "TestHandleUplink")}, devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-uplink"), applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-handle-uplink"), } h.InitStatus() dev := &device.Device{ AppID: appID, DevID: devID, AppEUI: appEUI, DevEUI: devEUI, } h.devices.Set(dev) defer func() { h.devices.Delete(appID, devID) }() h.applications.Set(&application.Application{ AppID: appID, }) defer func() { h.applications.Delete(appID) }() h.mqttUp = make(chan *types.UplinkMessage) h.mqttEvent = make(chan *types.DeviceEvent, 10) h.downlink = make(chan *pb_broker.DownlinkMessage) uplink, _ := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x4D, 0xDA, 0x23, 0x99, 0x61, 0xD4}) downlinkEmpty := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x21, 0xEA, 0x8B, 0x0E} downlinkACK := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x20, 0x00, 0x00, 0x0A, 0x3B, 0x3F, 0x77, 0x0B} downlinkMAC := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x05, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x0A, 0x4D, 0x11, 0x55, 0x01} expected := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x66, 0xE6, 0x1D, 0x49, 0x82, 0x84} downlink := &pb_broker.DownlinkMessage{ DownlinkOption: &pb_broker.DownlinkOption{ ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ FCnt: 0, }}}, }, } // Test Uplink, no downlink option available wg.Add(1) go func() { <-h.mqttUp wg.Done() }() err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) wg.WaitFor(50 * time.Millisecond) uplink.ResponseTemplate = downlink // Test Uplink, no downlink needed wg.Add(1) go func() { <-h.mqttUp wg.Done() }() downlink.Payload = downlinkEmpty err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) wg.WaitFor(50 * time.Millisecond) // Test Uplink, ACK downlink needed wg.Add(2) go func() { <-h.mqttUp wg.Done() }() go func() { <-h.downlink wg.Done() }() downlink.Payload = downlinkACK err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) wg.WaitFor(50 * time.Millisecond) // Test Uplink, MAC downlink needed wg.Add(2) go func() { <-h.mqttUp wg.Done() }() go func() { <-h.downlink wg.Done() }() downlink.Payload = downlinkMAC err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) wg.WaitFor(50 * time.Millisecond) dev.StartUpdate() dev.NextDownlink = &types.DownlinkMessage{ PayloadRaw: []byte{0xaa, 0xbc}, } // Test Uplink, Data downlink needed h.devices.Set(dev) wg.Add(2) go func() { <-h.mqttUp wg.Done() }() go func() { dl := <-h.downlink a.So(dl.Payload, ShouldResemble, expected) wg.Done() }() downlink.Payload = downlinkEmpty err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) wg.WaitFor(50 * time.Millisecond) dev, _ = h.devices.Get(appID, devID) a.So(dev.NextDownlink, ShouldBeNil) }
func TestHandleActivation(t *testing.T) { a := New(t) h := &handler{ Component: &component.Component{Ctx: GetLogger(t, "TestHandleActivation")}, applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-activation"), devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-activation"), } h.InitStatus() h.mqttEvent = make(chan *types.DeviceEvent, 10) var wg WaitGroup appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} appID := appEUI.String() devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} devID := devEUI.String() unknownDevEUI := types.DevEUI{8, 7, 6, 5, 4, 3, 2, 1} appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} h.applications.Set(&application.Application{ AppID: appID, }) defer func() { h.applications.Delete(appID) }() h.devices.Set(&device.Device{ AppID: appID, DevID: devID, AppEUI: appEUI, DevEUI: devEUI, AppKey: appKey, }) defer func() { h.devices.Delete(appID, devID) }() // Unknown res, err := doTestHandleActivation(h, appEUI, unknownDevEUI, [2]byte{1, 2}, appKey, ) a.So(err, ShouldNotBeNil) a.So(res, ShouldBeNil) // Wrong AppKey res, err = doTestHandleActivation(h, appEUI, devEUI, [2]byte{1, 2}, [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, ) a.So(err, ShouldNotBeNil) a.So(res, ShouldBeNil) wg.Add(1) go func() { <-h.mqttEvent wg.Done() }() // Known res, err = doTestHandleActivation(h, appEUI, devEUI, [2]byte{1, 2}, appKey, ) a.So(err, ShouldBeNil) a.So(res, ShouldNotBeNil) wg.WaitFor(50 * time.Millisecond) // Same DevNonce used twice res, err = doTestHandleActivation(h, appEUI, devEUI, [2]byte{1, 2}, appKey, ) a.So(err, ShouldNotBeNil) a.So(res, ShouldBeNil) wg.Add(1) go func() { <-h.mqttEvent wg.Done() }() // Other DevNonce res, err = doTestHandleActivation(h, appEUI, devEUI, [2]byte{2, 1}, appKey, ) a.So(err, ShouldBeNil) a.So(res, ShouldNotBeNil) wg.WaitFor(50 * time.Millisecond) // TODO: Validate response // TODO: Check DB }
func TestHandleDownlink(t *testing.T) { a := New(t) var err error var wg WaitGroup appID := "app2" devID := "dev2" appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) h := &handler{ Component: &component.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-downlink"), applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-enqueue-downlink"), downlink: make(chan *pb_broker.DownlinkMessage), mqttEvent: make(chan *types.DeviceEvent, 10), } h.InitStatus() // Neither payload nor Fields provided : ERROR err = h.HandleDownlink(&types.DownlinkMessage{ AppID: appID, DevID: devID, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, }) a.So(err, ShouldNotBeNil) h.devices.Set(&device.Device{ AppID: appID, DevID: devID, }) defer func() { h.devices.Delete(appID, devID) }() err = h.HandleDownlink(&types.DownlinkMessage{ AppID: appID, DevID: devID, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, }) a.So(err, ShouldBeNil) // Payload provided wg.Add(1) go func() { dl := <-h.downlink a.So(dl.Payload, ShouldNotBeEmpty) wg.Done() }() err = h.HandleDownlink(&types.DownlinkMessage{ AppID: appID, DevID: devID, PayloadRaw: []byte{0xAA, 0xBC}, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, DownlinkOption: &pb_broker.DownlinkOption{}, }) a.So(err, ShouldBeNil) wg.WaitFor(100 * time.Millisecond) // Both Payload and Fields provided h.applications.Set(&application.Application{ AppID: appID, Encoder: `function Encoder (payload){ return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0] }`, }) defer func() { h.applications.Delete(appID) }() jsonFields := map[string]interface{}{"temperature": 11} err = h.HandleDownlink(&types.DownlinkMessage{ FPort: 1, AppID: appID, DevID: devID, PayloadFields: jsonFields, PayloadRaw: []byte{0xAA, 0xBC}, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, }) a.So(err, ShouldNotBeNil) // JSON Fields provided wg.Add(1) go func() { dl := <-h.downlink a.So(dl.Payload, ShouldNotBeEmpty) wg.Done() }() err = h.HandleDownlink(&types.DownlinkMessage{ FPort: 1, AppID: appID, DevID: devID, PayloadFields: jsonFields, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, DownlinkOption: &pb_broker.DownlinkOption{}, }) a.So(err, ShouldBeNil) wg.WaitFor(100 * time.Millisecond) }