func TestApplicationMethods(t *testing.T) {
	conf := common.GetTestConfig()

	Convey("Given a clean database", t, func() {
		db, err := OpenDatabase(conf.PostgresDSN)
		So(err, ShouldBeNil)
		common.MustResetDB(db)

		Convey("When creating an application", func() {
			app := models.Application{
				AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
				Name:   "test app",
			}
			So(CreateApplication(db, app), ShouldBeNil)

			Convey("Then we can get it", func() {
				app2, err := GetApplication(db, app.AppEUI)
				So(err, ShouldBeNil)
				So(app2, ShouldResemble, app)
			})

			Convey("Then get applications returns a single item", func() {
				apps, err := GetApplications(db, 10, 0)
				So(err, ShouldBeNil)
				So(apps, ShouldHaveLength, 1)
				So(apps[0], ShouldResemble, app)
			})

			Convey("Then applications count returns 1", func() {
				count, err := GetApplicationsCount(db)
				So(err, ShouldBeNil)
				So(count, ShouldEqual, 1)
			})

			Convey("When updating the application", func() {
				app.Name = "new name"
				So(UpdateApplication(db, app), ShouldBeNil)

				Convey("The application has been updated", func() {
					app2, err := GetApplication(db, app.AppEUI)
					So(err, ShouldBeNil)
					So(app2, ShouldResemble, app)
				})
			})

			Convey("After deleting the application", func() {
				So(DeleteApplication(db, app.AppEUI), ShouldBeNil)

				Convey("The application has gone", func() {
					count, err := GetApplicationsCount(db)
					So(err, ShouldBeNil)
					So(count, ShouldEqual, 0)
				})
			})
		})
	})
}
func TestApplicationAPI(t *testing.T) {
	conf := common.GetTestConfig()

	Convey("Given a clean database and api instance", t, func() {
		db, err := storage.OpenDatabase(conf.PostgresDSN)
		So(err, ShouldBeNil)
		common.MustResetDB(db)

		ctx := context.Background()
		lsCtx := loraserver.Context{DB: db}

		api := NewApplicationAPI(lsCtx)

		Convey("When creating an application", func() {
			_, err := api.Create(ctx, &pb.CreateApplicationRequest{AppEUI: "0102030405060708", Name: "test app"})
			So(err, ShouldBeNil)

			Convey("Then we can get it", func() {
				resp, err := api.Get(ctx, &pb.GetApplicationRequest{AppEUI: "0102030405060708"})
				So(err, ShouldBeNil)
				So(resp, ShouldResemble, &pb.GetApplicationResponse{AppEUI: "0102030405060708", Name: "test app"})
			})

			Convey("Then listing the applications returns a single item", func() {
				resp, err := api.List(ctx, &pb.ListApplicationRequest{Limit: 10})
				So(err, ShouldBeNil)
				So(resp.Result, ShouldHaveLength, 1)
				So(resp.TotalCount, ShouldEqual, 1)
				So(resp.Result[0], ShouldResemble, &pb.GetApplicationResponse{AppEUI: "0102030405060708", Name: "test app"})
			})

			Convey("When updating the application", func() {
				_, err := api.Update(ctx, &pb.UpdateApplicationRequest{AppEUI: "0102030405060708", Name: "test app 2"})
				So(err, ShouldBeNil)

				Convey("Then the application has been updated", func() {
					resp, err := api.Get(ctx, &pb.GetApplicationRequest{AppEUI: "0102030405060708"})
					So(err, ShouldBeNil)
					So(resp.Name, ShouldEqual, "test app 2")
				})
			})

			Convey("After deleting the application", func() {
				_, err := api.Delete(ctx, &pb.DeleteApplicationRequest{AppEUI: "0102030405060708"})
				So(err, ShouldBeNil)

				Convey("Then listing the applications resturns zero items", func() {
					resp, err := api.List(ctx, &pb.ListApplicationRequest{Limit: 10})
					So(err, ShouldBeNil)
					So(resp.Result, ShouldHaveLength, 0)
				})
			})
		})
	})
}
Beispiel #3
0
func TestNodeMethods(t *testing.T) {
	conf := common.GetTestConfig()

	Convey("Given a clean database with application", t, func() {
		db, err := OpenDatabase(conf.PostgresDSN)
		So(err, ShouldBeNil)
		common.MustResetDB(db)

		app := models.Application{
			AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
			Name:   "test app",
		}
		So(CreateApplication(db, app), ShouldBeNil)

		Convey("When creating a node", func() {
			node := models.Node{
				DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1},
				AppEUI: app.AppEUI,
				AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8},

				RXDelay:     2,
				RX1DROffset: 3,
			}
			So(CreateNode(db, node), ShouldBeNil)

			Convey("Whe can get it", func() {
				node2, err := GetNode(db, node.DevEUI)
				node2.UsedDevNonces = nil
				So(err, ShouldBeNil)
				So(node2, ShouldResemble, node)
			})

			Convey("Then get nodes returns a single item", func() {
				nodes, err := GetNodes(db, 10, 0)
				So(err, ShouldBeNil)
				So(nodes, ShouldHaveLength, 1)
				nodes[0].UsedDevNonces = nil
				So(nodes[0], ShouldResemble, node)
			})

			Convey("Then get nodes for AppEUI returns a single item", func() {
				nodes, err := GetNodesForAppEUI(db, app.AppEUI, 10, 0)
				So(err, ShouldBeNil)
				So(nodes, ShouldHaveLength, 1)
				nodes[0].UsedDevNonces = nil
				So(nodes[0], ShouldResemble, node)
			})

			Convey("Then get nodes count returns 1", func() {
				count, err := GetNodesCount(db)
				So(err, ShouldBeNil)
				So(count, ShouldEqual, 1)
			})

			Convey("Then get nodes count for AppEUI returns 1", func() {
				count, err := GetNodesForAppEUICount(db, app.AppEUI)
				So(err, ShouldBeNil)
				So(count, ShouldEqual, 1)
			})

			Convey("When updating the node", func() {
				node.AppKey = [16]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
				So(UpdateNode(db, node), ShouldBeNil)

				Convey("Then the nodes has been updated", func() {
					node2, err := GetNode(db, node.DevEUI)
					So(err, ShouldBeNil)
					node2.UsedDevNonces = nil
					So(node2, ShouldResemble, node)
				})
			})

			Convey("When deleting the node", func() {
				So(DeleteNode(db, node.DevEUI), ShouldBeNil)

				Convey("Then get nodes count returns 0", func() {
					count, err := GetNodesCount(db)
					So(err, ShouldBeNil)
					So(count, ShouldEqual, 0)
				})
			})
		})
	})
}
func TestNodeSessionAPI(t *testing.T) {
	conf := common.GetTestConfig()

	Convey("Given a clean database and api instance", t, func() {
		db, err := storage.OpenDatabase(conf.PostgresDSN)
		So(err, ShouldBeNil)
		common.MustResetDB(db)

		p := storage.NewRedisPool(conf.RedisURL)
		common.MustFlushRedis(p)

		lsCtx := loraserver.Context{DB: db, RedisPool: p, NetID: [3]byte{1, 2, 3}}
		ctx := context.Background()
		api := NewNodeSessionAPI(lsCtx)

		Convey("Given an application and node are created (fk constraints)", func() {
			app := models.Application{
				AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
				Name:   "test app",
			}
			So(storage.CreateApplication(db, app), ShouldBeNil)

			node := models.Node{
				DevEUI:        [8]byte{8, 7, 6, 5, 4, 3, 2, 1},
				AppEUI:        [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
				AppKey:        [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
				UsedDevNonces: [][2]byte{},
			}
			So(storage.CreateNode(db, node), ShouldBeNil)

			Convey("When creating a node-session", func() {
				_, err := api.Create(ctx, &pb.CreateNodeSessionRequest{
					DevAddr:     "06020304",
					DevEUI:      node.DevEUI.String(),
					AppEUI:      node.AppEUI.String(),
					AppSKey:     node.AppKey.String(),
					NwkSKey:     node.AppKey.String(),
					FCntUp:      10,
					FCntDown:    11,
					RxDelay:     1,
					Rx1DROffset: 2,
					CFList: []uint32{
						868700000,
					},
				})
				So(err, ShouldBeNil)

				Convey("Then it can be retrieved by DevAddr", func() {
					resp, err := api.Get(ctx, &pb.GetNodeSessionRequest{DevAddr: "06020304"})
					So(err, ShouldBeNil)
					So(resp, ShouldResemble, &pb.GetNodeSessionResponse{
						DevAddr:     "06020304",
						DevEUI:      node.DevEUI.String(),
						AppEUI:      node.AppEUI.String(),
						AppSKey:     node.AppKey.String(),
						NwkSKey:     node.AppKey.String(),
						FCntUp:      10,
						FCntDown:    11,
						RxDelay:     1,
						Rx1DROffset: 2,
						CFList: []uint32{
							868700000,
							0,
							0,
							0,
							0,
						},
					})
				})

				Convey("Then it can be retrieved by DevEUI", func() {
					resp, err := api.GetByDevEUI(ctx, &pb.GetNodeSessionByDevEUIRequest{DevEUI: node.DevEUI.String()})
					So(err, ShouldBeNil)
					So(resp, ShouldResemble, &pb.GetNodeSessionResponse{
						DevAddr:     "06020304",
						DevEUI:      node.DevEUI.String(),
						AppEUI:      node.AppEUI.String(),
						AppSKey:     node.AppKey.String(),
						NwkSKey:     node.AppKey.String(),
						FCntUp:      10,
						FCntDown:    11,
						RxDelay:     1,
						Rx1DROffset: 2,
						CFList: []uint32{
							868700000,
							0,
							0,
							0,
							0,
						},
					})
				})

				Convey("When updating the node-session", func() {
					_, err := api.Update(ctx, &pb.UpdateNodeSessionRequest{
						DevAddr:     "06020304",
						DevEUI:      node.DevEUI.String(),
						AppEUI:      node.AppEUI.String(),
						AppSKey:     node.AppKey.String(),
						NwkSKey:     node.AppKey.String(),
						FCntUp:      20,
						FCntDown:    22,
						RxDelay:     10,
						Rx1DROffset: 20,
						CFList: []uint32{
							868700000,
							868800000,
						},
					})
					So(err, ShouldBeNil)

					Convey("Then the node-session has been updated", func() {
						resp, err := api.Get(ctx, &pb.GetNodeSessionRequest{DevAddr: "06020304"})
						So(err, ShouldBeNil)
						So(resp, ShouldResemble, &pb.GetNodeSessionResponse{
							DevAddr:     "06020304",
							DevEUI:      node.DevEUI.String(),
							AppEUI:      node.AppEUI.String(),
							AppSKey:     node.AppKey.String(),
							NwkSKey:     node.AppKey.String(),
							FCntUp:      20,
							FCntDown:    22,
							RxDelay:     10,
							Rx1DROffset: 20,
							CFList: []uint32{
								868700000,
								868800000,
								0,
								0,
								0,
							},
						})
					})
				})

				Convey("When deleting the node-session", func() {
					_, err := api.Delete(ctx, &pb.DeleteNodeSessionRequest{DevAddr: "06020304"})
					So(err, ShouldBeNil)

					Convey("Then the node-session has been deleted", func() {
						_, err := api.Get(ctx, &pb.GetNodeSessionRequest{DevAddr: "06020304"})
						So(err, ShouldNotBeNil)
					})
				})
			})
		})
	})
}
func TestHandleJoinRequestPackets(t *testing.T) {
	conf := common.GetTestConfig()

	Convey("Given a dummy gateway and application backend and a clean Postgres and Redis database", t, func() {
		a := &testApplicationBackend{
			rxPayloadChan:           make(chan models.RXPayload, 1),
			notificationPayloadChan: make(chan interface{}, 10),
		}
		g := &testGatewayBackend{
			rxPacketChan: make(chan models.RXPacket, 1),
			txPacketChan: make(chan models.TXPacket, 1),
		}
		p := storage.NewRedisPool(conf.RedisURL)
		common.MustFlushRedis(p)
		db, err := storage.OpenDatabase(conf.PostgresDSN)
		So(err, ShouldBeNil)
		common.MustResetDB(db)

		ctx := Context{
			RedisPool:   p,
			Gateway:     g,
			Application: a,
			DB:          db,
		}

		Convey("Given a node and application in the database", func() {
			app := models.Application{
				AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
				Name:   "test app",
			}
			So(storage.CreateApplication(ctx.DB, app), ShouldBeNil)

			node := models.Node{
				DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1},
				AppEUI: app.AppEUI,
				AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},

				RXDelay:     3,
				RX1DROffset: 2,
			}
			So(storage.CreateNode(ctx.DB, node), ShouldBeNil)

			Convey("Given a JoinRequest with correct DevEUI but incorrect AppEUI", func() {
				phy := lorawan.PHYPayload{
					MHDR: lorawan.MHDR{
						MType: lorawan.JoinRequest,
						Major: lorawan.LoRaWANR1,
					},
					MACPayload: &lorawan.JoinRequestPayload{
						AppEUI:   [8]byte{1, 2, 3, 4, 5, 6, 7, 9},
						DevEUI:   node.DevEUI,
						DevNonce: [2]byte{1, 2},
					},
				}
				So(phy.SetMIC(node.AppKey), ShouldBeNil)

				rxPacket := models.RXPacket{
					PHYPayload: phy,
					RXInfo: models.RXInfo{
						Frequency: common.Band.UplinkChannels[0].Frequency,
						DataRate:  common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]],
					},
				}

				Convey("then handleRXPacket returns an error", func() {
					So(handleRXPacket(ctx, rxPacket), ShouldResemble, errors.New("node 0807060504030201 belongs to application 0102030405060708, 0102030405060709 was given"))
				})
			})

			Convey("Given a JoinRequest packet", func() {
				phy := lorawan.PHYPayload{
					MHDR: lorawan.MHDR{
						MType: lorawan.JoinRequest,
						Major: lorawan.LoRaWANR1,
					},
					MACPayload: &lorawan.JoinRequestPayload{
						AppEUI:   app.AppEUI,
						DevEUI:   node.DevEUI,
						DevNonce: [2]byte{1, 2},
					},
				}
				So(phy.SetMIC(node.AppKey), ShouldBeNil)

				rxPacket := models.RXPacket{
					PHYPayload: phy,
					RXInfo: models.RXInfo{
						Frequency: common.Band.UplinkChannels[0].Frequency,
						DataRate:  common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]],
					},
				}

				Convey("When calling handleRXPacket", func() {
					So(handleRXPacket(ctx, rxPacket), ShouldBeNil)

					Convey("Then a JoinAccept was sent to the node", func() {
						txPacket := <-g.txPacketChan
						phy := txPacket.PHYPayload
						So(phy.DecryptJoinAcceptPayload(node.AppKey), ShouldBeNil)
						So(phy.MHDR.MType, ShouldEqual, lorawan.JoinAccept)

						Convey("Then it was sent after 5s", func() {
							So(txPacket.TXInfo.Timestamp, ShouldEqual, rxPacket.RXInfo.Timestamp+uint32(5*time.Second/time.Microsecond))
						})

						Convey("Then the RXDelay is set to 3s", func() {
							jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload)
							So(jaPL.RXDelay, ShouldEqual, 3)
						})

						Convey("Then the DLSettings are set correctly", func() {
							jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload)
							So(jaPL.DLSettings.RX2DataRate, ShouldEqual, uint8(common.Band.RX2DataRate))
							So(jaPL.DLSettings.RX1DROffset, ShouldEqual, node.RX1DROffset)
						})

						Convey("Then a node-session was created", func() {
							jaPL := phy.MACPayload.(*lorawan.JoinAcceptPayload)

							_, err := storage.GetNodeSession(ctx.RedisPool, jaPL.DevAddr)
							So(err, ShouldBeNil)
						})

						Convey("Then the dev-nonce was added to the used dev-nonces", func() {
							node, err := storage.GetNode(ctx.DB, node.DevEUI)
							So(err, ShouldBeNil)
							So([2]byte{1, 2}, ShouldBeIn, node.UsedDevNonces)
						})

						Convey("Then a join notification was sent to the application", func() {
							notification := <-a.notificationPayloadChan
							join, ok := notification.(models.JoinNotification)
							So(ok, ShouldBeTrue)
							So(join.DevEUI, ShouldResemble, node.DevEUI)
						})
					})
				})
			})
		})
	})
}
Beispiel #6
0
func TestNodeAPI(t *testing.T) {
	conf := common.GetTestConfig()

	Convey("Given a clean database with an application and api instance", t, func() {
		db, err := storage.OpenDatabase(conf.PostgresDSN)
		So(err, ShouldBeNil)
		common.MustResetDB(db)
		p := storage.NewRedisPool(conf.RedisURL)
		common.MustFlushRedis(p)

		ctx := context.Background()
		lsCtx := loraserver.Context{DB: db, RedisPool: p}
		api := NewNodeAPI(lsCtx)

		app := models.Application{
			AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
			Name:   "test app",
		}
		So(storage.CreateApplication(db, app), ShouldBeNil)

		Convey("When creating a node", func() {
			_, err := api.Create(ctx, &pb.CreateNodeRequest{
				DevEUI:      "0807060504030201",
				AppEUI:      "0102030405060708",
				AppKey:      "01020304050607080102030405060708",
				RxDelay:     1,
				Rx1DROffset: 3,
			})
			So(err, ShouldBeNil)

			Convey("The node has been created", func() {
				node, err := api.Get(ctx, &pb.GetNodeRequest{DevEUI: "0807060504030201"})
				So(err, ShouldBeNil)
				So(node, ShouldResemble, &pb.GetNodeResponse{
					DevEUI:      "0807060504030201",
					AppEUI:      "0102030405060708",
					AppKey:      "01020304050607080102030405060708",
					RxDelay:     1,
					Rx1DROffset: 3,
				})
			})

			Convey("Then listing the nodes returns a single items", func() {
				nodes, err := api.List(ctx, &pb.ListNodeRequest{
					Limit: 10,
				})
				So(err, ShouldBeNil)
				So(nodes.Result, ShouldHaveLength, 1)
				So(nodes.TotalCount, ShouldEqual, 1)
				So(nodes.Result[0], ShouldResemble, &pb.GetNodeResponse{
					DevEUI:      "0807060504030201",
					AppEUI:      "0102030405060708",
					AppKey:      "01020304050607080102030405060708",
					RxDelay:     1,
					Rx1DROffset: 3,
				})
			})

			Convey("Then listing the nodes for a given AppEUI returns a single item", func() {
				nodes, err := api.ListByAppEUI(ctx, &pb.ListNodeByAppEUIRequest{
					Limit:  10,
					AppEUI: "0102030405060708",
				})
				So(err, ShouldBeNil)
				So(nodes.Result, ShouldHaveLength, 1)
				So(nodes.TotalCount, ShouldEqual, 1)
				So(nodes.Result[0], ShouldResemble, &pb.GetNodeResponse{
					DevEUI:      "0807060504030201",
					AppEUI:      "0102030405060708",
					AppKey:      "01020304050607080102030405060708",
					RxDelay:     1,
					Rx1DROffset: 3,
				})
			})

			Convey("When updating the node", func() {
				_, err := api.Update(ctx, &pb.UpdateNodeRequest{
					DevEUI:      "0807060504030201",
					AppEUI:      "0102030405060708",
					AppKey:      "08070605040302010807060504030201",
					RxDelay:     3,
					Rx1DROffset: 1,
				})
				So(err, ShouldBeNil)

				Convey("Then the node has been updated", func() {
					node, err := api.Get(ctx, &pb.GetNodeRequest{DevEUI: "0807060504030201"})
					So(err, ShouldBeNil)
					So(node, ShouldResemble, &pb.GetNodeResponse{
						DevEUI:      "0807060504030201",
						AppEUI:      "0102030405060708",
						AppKey:      "08070605040302010807060504030201",
						RxDelay:     3,
						Rx1DROffset: 1,
					})
				})
			})

			Convey("After deleting the node", func() {
				_, err := api.Delete(ctx, &pb.DeleteNodeRequest{DevEUI: "0807060504030201"})
				So(err, ShouldBeNil)

				Convey("Then listing the nodes returns zero nodes", func() {
					nodes, err := api.List(ctx, &pb.ListNodeRequest{Limit: 10})
					So(err, ShouldBeNil)
					So(nodes.TotalCount, ShouldEqual, 0)
					So(nodes.Result, ShouldHaveLength, 0)
				})
			})

			Convey("Given a tx payload in the the queue", func() {
				So(storage.AddTXPayloadToQueue(p, models.TXPayload{
					DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1},
					Data:   []byte("hello!"),
				}), ShouldBeNil)
				count, err := storage.GetTXPayloadQueueSize(p, [8]byte{8, 7, 6, 5, 4, 3, 2, 1})
				So(err, ShouldBeNil)
				So(count, ShouldEqual, 1)

				Convey("When flushing the tx-payload queue", func() {
					_, err := api.FlushTXPayloadQueue(ctx, &pb.FlushTXPayloadQueueRequest{DevEUI: "0807060504030201"})
					So(err, ShouldBeNil)

					Convey("Then the queue is empty", func() {
						count, err := storage.GetTXPayloadQueueSize(p, [8]byte{8, 7, 6, 5, 4, 3, 2, 1})
						So(err, ShouldBeNil)
						So(count, ShouldEqual, 0)
					})
				})
			})
		})
	})
}
func TestChannelFunctions(t *testing.T) {
	conf := common.GetTestConfig()

	Convey("Given a clean database", t, func() {
		db, err := OpenDatabase(conf.PostgresDSN)
		So(err, ShouldBeNil)
		common.MustResetDB(db)

		Convey("When creating a channel-list", func() {
			cl := models.ChannelList{
				Name: "test channel-list",
			}
			So(CreateChannelList(db, &cl), ShouldBeNil)

			Convey("Then the channel-list exists", func() {
				cl2, err := GetChannelList(db, cl.ID)
				So(err, ShouldBeNil)
				So(cl2, ShouldResemble, cl)
			})

			Convey("When updating the channel-list", func() {
				cl.Name = "test channel-list changed"
				So(UpdateChannelList(db, cl), ShouldBeNil)

				Convey("Then the channel-list has been updated", func() {
					cl2, err := GetChannelList(db, cl.ID)
					So(err, ShouldBeNil)
					So(cl2, ShouldResemble, cl)
				})
			})

			Convey("Then listing the channel-lists returns 1 result", func() {
				lists, err := GetChannelLists(db, 10, 0)
				So(err, ShouldBeNil)
				So(lists, ShouldHaveLength, 1)
				So(lists[0], ShouldResemble, cl)
			})

			Convey("Then the channel-list count returns 1", func() {
				count, err := GetChannelListsCount(db)
				So(err, ShouldBeNil)
				So(count, ShouldEqual, 1)
			})

			Convey("When deleting the channel-list", func() {
				So(DeleteChannelList(db, cl.ID), ShouldBeNil)

				Convey("Then the channel-list has been removed", func() {
					count, err := GetChannelListsCount(db)
					So(err, ShouldBeNil)
					So(count, ShouldEqual, 0)
				})
			})

			Convey("When creating a channel", func() {
				c := models.Channel{
					ChannelListID: cl.ID,
					Channel:       4,
					Frequency:     868700000,
				}
				So(CreateChannel(db, &c), ShouldBeNil)

				Convey("Then the channel has been created", func() {
					c2, err := GetChannel(db, c.ID)
					So(err, ShouldBeNil)
					So(c2, ShouldResemble, c)
				})

				Convey("When updating the channel", func() {
					c.Channel = 5
					So(UpdateChannel(db, c), ShouldBeNil)

					Convey("Then the channel has been updated", func() {
						c2, err := GetChannel(db, c.ID)
						So(err, ShouldBeNil)
						So(c2, ShouldResemble, c)
					})
				})

				Convey("When deleting the channel", func() {
					So(DeleteChannel(db, c.ID), ShouldBeNil)

					Convey("Then the channel has been deleted", func() {
						_, err := GetChannel(db, c.ID)
						So(err, ShouldNotBeNil)
					})
				})

				Convey("When getting all channels for the channel-list", func() {
					channels, err := GetChannelsForChannelList(db, cl.ID)
					So(err, ShouldBeNil)

					Convey("Then it contains the channel", func() {
						So(channels, ShouldHaveLength, 1)
						So(channels[0], ShouldResemble, c)
					})
				})
			})
		})
	})
}
func TestChannelListAndChannelAPI(t *testing.T) {
	conf := common.GetTestConfig()

	Convey("Given a clean database and api instances", t, func() {
		db, err := storage.OpenDatabase(conf.PostgresDSN)
		So(err, ShouldBeNil)
		common.MustResetDB(db)

		ctx := context.Background()
		lsCtx := loraserver.Context{DB: db}

		cAPI := NewChannelAPI(lsCtx)
		clAPI := NewChannelListAPI(lsCtx)

		Convey("When creating a channel-list", func() {
			resp, err := clAPI.Create(ctx, &pb.CreateChannelListRequest{Name: "test channel-list"})
			So(err, ShouldBeNil)

			clID := resp.Id

			Convey("Then the channel-list has been created", func() {
				cl, err := clAPI.Get(ctx, &pb.GetChannelListRequest{Id: clID})
				So(err, ShouldBeNil)
				So(cl, ShouldResemble, &pb.GetChannelListResponse{Id: clID, Name: "test channel-list"})
			})

			Convey("When updating the channel-list", func() {
				_, err := clAPI.Update(ctx, &pb.UpdateChannelListRequest{Id: clID, Name: "test channel-list changed"})
				So(err, ShouldBeNil)

				Convey("Then the channel-list has been updated", func() {
					cl, err := clAPI.Get(ctx, &pb.GetChannelListRequest{Id: clID})
					So(err, ShouldBeNil)
					So(cl, ShouldResemble, &pb.GetChannelListResponse{Id: clID, Name: "test channel-list changed"})
				})
			})

			Convey("Then listing the channel-lists returns 1 result", func() {
				resp, err := clAPI.List(ctx, &pb.ListChannelListRequest{Limit: 10, Offset: 0})
				So(err, ShouldBeNil)

				So(resp.TotalCount, ShouldEqual, 1)
				So(resp.Result, ShouldHaveLength, 1)
				So(resp.Result[0], ShouldResemble, &pb.GetChannelListResponse{Id: clID, Name: "test channel-list"})
			})

			Convey("When deleting the channel-list", func() {
				_, err := clAPI.Delete(ctx, &pb.DeleteChannelListRequest{Id: clID})
				So(err, ShouldBeNil)

				Convey("Then the channel-list has been deleted", func() {
					resp, err := clAPI.List(ctx, &pb.ListChannelListRequest{Limit: 10, Offset: 0})
					So(err, ShouldBeNil)

					So(resp.TotalCount, ShouldEqual, 0)
				})
			})

			Convey("When creating a channel", func() {
				resp, err := cAPI.Create(ctx, &pb.CreateChannelRequest{
					ChannelListID: clID,
					Channel:       4,
					Frequency:     868700000,
				})
				So(err, ShouldBeNil)
				cID := resp.Id

				Convey("Then the channel has been created", func() {
					resp, err := cAPI.Get(ctx, &pb.GetChannelRequest{Id: cID})
					So(err, ShouldBeNil)
					So(resp, ShouldResemble, &pb.GetChannelResponse{
						Id:            cID,
						ChannelListID: clID,
						Channel:       4,
						Frequency:     868700000,
					})

					Convey("When updating the channel", func() {
						_, err := cAPI.Update(ctx, &pb.UpdateChannelRequest{
							Id:            cID,
							ChannelListID: clID,
							Channel:       5,
							Frequency:     868700000,
						})
						So(err, ShouldBeNil)

						Convey("Then the channel has been updated", func() {
							resp, err := cAPI.Get(ctx, &pb.GetChannelRequest{Id: cID})
							So(err, ShouldBeNil)
							So(resp, ShouldResemble, &pb.GetChannelResponse{
								Id:            cID,
								ChannelListID: clID,
								Channel:       5,
								Frequency:     868700000,
							})
						})
					})

					Convey("When deleting the channel", func() {
						_, err := cAPI.Delete(ctx, &pb.DeleteChannelRequest{Id: cID})
						So(err, ShouldBeNil)

						Convey("Then the channel has been deleted", func() {
							_, err := cAPI.Get(ctx, &pb.GetChannelRequest{Id: cID})
							So(err, ShouldNotBeNil)
						})
					})

					Convey("When getting all channels for the channel-list", func() {
						resp, err := cAPI.ListByChannelList(ctx, &pb.ListChannelsByChannelListRequest{Id: clID})
						So(err, ShouldBeNil)

						Convey("Then it contains the channel", func() {
							So(resp.Result, ShouldHaveLength, 1)
							So(resp.Result[0], ShouldResemble, &pb.GetChannelResponse{
								Id:            cID,
								ChannelListID: clID,
								Channel:       4,
								Frequency:     868700000,
							})
						})
					})
				})
			})
		})
	})
}