Skip to content

Commit

Permalink
Merge pull request #58 from xmidt-org/mocktr181
Browse files Browse the repository at this point in the history
Mocktr181
  • Loading branch information
schmidtw authored Apr 4, 2024
2 parents 2e9648e + fac7ae5 commit 55007a0
Show file tree
Hide file tree
Showing 9 changed files with 57,043 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ internal/adapters/libparodus/cmd/example/example
dist/*

cmd/xmidt-agent/dist/*
lint.sh
4 changes: 4 additions & 0 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ License: Apache-2.0
Files: .whitesource
Copyright: SPDX-FileCopyrightText: 2023 Comcast Cable Communications Management, LLC
License: Apache-2.0

Files: *.json
Copyright: SPDX-FileCopyrightText: 2023 Comcast Cable Communications Management, LLC
License: Apache-2.0
10 changes: 10 additions & 0 deletions cmd/xmidt-agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Config struct {
XmidtService XmidtService
Logger sallust.Config
Storage Storage
MockTr181 MockTr181
}

type Websocket struct {
Expand Down Expand Up @@ -173,6 +174,11 @@ type Storage struct {
Durable string
}

type MockTr181 struct {
FilePath string
Enabled bool
}

// Collect and process the configuration files and env vars and
// produce a configuration object.
func provideConfig(cli *CLI) (*goschtalt.Config, error) {
Expand Down Expand Up @@ -290,4 +296,8 @@ var defaultConfig = Config{
MaxInterval: 341*time.Second + 333*time.Millisecond,
},
},
MockTr181: MockTr181{
FilePath: "./mock_tr181.json",
Enabled: true,
},
}
1 change: 1 addition & 0 deletions cmd/xmidt-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func xmidtAgent(args []string) (*fx.App, error) {
goschtalt.UnmarshalFunc[XmidtService]("xmidt_service"),
goschtalt.UnmarshalFunc[Storage]("storage"),
goschtalt.UnmarshalFunc[Websocket]("websocket"),
goschtalt.UnmarshalFunc[MockTr181]("mocktr181"),
),

fsProvide(),
Expand Down
207 changes: 207 additions & 0 deletions internal/wrphandlers/mocktr181/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0

package mocktr181

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"

"github.com/xmidt-org/wrp-go/v3"
"github.com/xmidt-org/xmidt-agent/internal/wrpkit"
)

var (
ErrInvalidInput = fmt.Errorf("invalid input")
ErrInvalidFileInput = fmt.Errorf("misconfigured file input")
ErrUnableToReadFile = fmt.Errorf("unable to read file")
ErrInvalidPayload = fmt.Errorf("invalid request payload")
ErrInvalidResponsePayload = fmt.Errorf("invalid response payload")
)

// Option is a functional option type for WS.
type Option interface {
apply(*Handler) error
}

type optionFunc func(*Handler) error

func (f optionFunc) apply(c *Handler) error {
return f(c)
}

type Handler struct {
egress wrpkit.Handler
source string
filePath string
parameters []MockParameter
enabled bool
}

type MockParameter struct {
Name string
Value string
Access string
DataType int // add json labels here
Attributes map[string]interface{}
Delay int
}

type MockParameters struct {
Parameters []MockParameter
}

type Tr181Payload struct {
Command string `json:"command"`
Names []string `json:"names"`
Parameters []Parameter `json:"parameters"`
}

type Parameters struct {
Parameters []Parameter
}

type Parameter struct {
Name string `json:"name"`
Value string `json:"value"`
DataType int `json:"dataType"`
Attributes map[string]interface{} `json:"attributes"`
}

// New creates a new instance of the Handler struct. The parameter egress is
// the handler that will be called to send the response. The parameter source is the source to use in
// the response message.
func New(egress wrpkit.Handler, source string, opts ...Option) (*Handler, error) {
// TODO - load config from file system

h := Handler{
egress: egress,
source: source,
}

for _, opt := range opts {
if opt != nil {
if err := opt.apply(&h); err != nil {
return nil, err
}
}
}

parameters, err := h.loadFile()
if err != nil {
return nil, errors.Join(ErrUnableToReadFile, err)
}

h.parameters = parameters

if h.egress == nil || h.source == "" {
return nil, ErrInvalidInput
}

return &h, nil
}

func (h Handler) Enabled() bool {
return h.enabled
}

// HandleWrp is called to process a tr181 command
func (h Handler) HandleWrp(msg wrp.Message) error {
payload := new(Tr181Payload)
var commandErr error
err := json.Unmarshal(msg.Payload, &payload)
if err != nil {
return err
}

var payloadResponse []byte
var statusCode int64

command := payload.Command

switch command {
case "GET":
statusCode, payloadResponse, commandErr = h.get(payload.Names)

case "SET":
statusCode = h.set(payload.Parameters)

default:
// currently only get and set are implemented for existing mocktr181
statusCode = http.StatusOK
}

response := msg
response.Destination = msg.Source
response.Source = h.source
response.ContentType = "text/plain"
response.Payload = payloadResponse

response.Status = &statusCode

err = h.egress.HandleWrp(response)

return errors.Join(err, commandErr)
}

func (h Handler) get(names []string) (int64, []byte, error) {
result := []Parameter{}

for _, name := range names {
for _, mockParameter := range h.parameters {
if strings.HasPrefix(mockParameter.Name, name) {
result = append(result, Parameter{
Name: mockParameter.Name,
Value: mockParameter.Value,
DataType: mockParameter.DataType,
Attributes: mockParameter.Attributes,
})
}
}
}

payload, err := json.Marshal(result)
if err != nil {
return http.StatusInternalServerError, payload, errors.Join(ErrInvalidResponsePayload, err)
}

return http.StatusOK, payload, nil
}

func (h Handler) set(parameters []Parameter) int64 {
for _, parameter := range parameters {
for _, mockParameter := range h.parameters {
if strings.HasPrefix(mockParameter.Name, parameter.Name) {
if mockParameter.Access == "rw" {
mockParameter.Value = parameter.Value
mockParameter.DataType = parameter.DataType
mockParameter.Attributes = parameter.Attributes
}
}
}
}

return http.StatusAccepted
}

func (h Handler) loadFile() ([]MockParameter, error) {
jsonFile, err := os.Open(h.filePath)
if err != nil {
return nil, errors.Join(ErrUnableToReadFile, err)
}
defer jsonFile.Close()

var parameters []MockParameter
byteValue, _ := io.ReadAll(jsonFile)
err = json.Unmarshal(byteValue, &parameters)
if err != nil {
return nil, errors.Join(ErrInvalidFileInput, err)
}

return parameters, nil
}
108 changes: 108 additions & 0 deletions internal/wrphandlers/mocktr181/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0

package mocktr181

import (
"encoding/json"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xmidt-org/wrp-go/v3"
"github.com/xmidt-org/xmidt-agent/internal/wrpkit"
)

func TestHandler_HandleWrp(t *testing.T) {
tests := []struct {
description string
nextResult error
nextCallCount int
egressResult error
egressCallCount int
msg wrp.Message
expectedErr error
validate func(*assert.Assertions, wrp.Message, *Handler) error
}{
{
description: "get success with multiple results",
egressCallCount: 1,
expectedErr: nil,
msg: wrp.Message{
Type: wrp.SimpleEventMessageType,
Source: "dns:tr1d1um.example.com/service/ignored",
Destination: "event:event_1/ignored",
Payload: []byte("{\"command\":\"GET\",\"names\":[\"Device.DeviceInfo.\"]}"),
},
validate: func(a *assert.Assertions, msg wrp.Message, h *Handler) error {
a.Equal(int64(http.StatusOK), *msg.Status)
var result []Parameters
err := json.Unmarshal(msg.Payload, &result)
a.NoError(err)
a.Equal(102, len(result))
a.True(h.Enabled())
return nil
},
}, {
description: "set, success",
egressCallCount: 1,
msg: wrp.Message{
Type: wrp.SimpleEventMessageType,
Source: "dns:tr1d1um.example.com/service/ignored",
Destination: "event:event_1/ignored",
Payload: []byte("{\"command\":\"SET\",\"parameters\":[{\"name\":\"Device.WiFi.Radio.10000.Name\",\"dataType\":0,\"value\":\"anothername\",\"attributes\":{\"notify\":0}}]}"),
},
validate: func(a *assert.Assertions, msg wrp.Message, h *Handler) error {
a.Equal(int64(http.StatusAccepted), *msg.Status)
a.True(h.Enabled())

return nil
},
}, {
description: "set, read only",
egressCallCount: 1,
msg: wrp.Message{
Type: wrp.SimpleEventMessageType,
Source: "dns:tr1d1um.example.com/service/ignored",
Destination: "event:event_1/ignored",
Payload: []byte("{\"command\":\"SET\",\"parameters\":[{\"name\":\"Device.Bridging.MaxBridgeEntries\",\"dataType\":0,\"value\":\"anothername\",\"attributes\":{\"notify\":0}}]}"),
},
validate: func(a *assert.Assertions, msg wrp.Message, h *Handler) error {
a.Equal(int64(http.StatusAccepted), *msg.Status)
a.True(h.Enabled())

return nil
},
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
var h *Handler

egressCallCount := 0
egress := wrpkit.HandlerFunc(func(msg wrp.Message) error {
egressCallCount++
if tc.validate != nil {
assert.NoError(tc.validate(assert, msg, h))
}
return tc.egressResult
})

mockDefaults := []Option{
FilePath("mock_tr181_test.json"),
Enabled(true),
}

h, err := New(egress, "some-source", mockDefaults...)
require.NoError(err)

err = h.HandleWrp(tc.msg)
assert.ErrorIs(err, tc.expectedErr)

assert.Equal(tc.egressCallCount, egressCallCount)
})
}
}
Loading

0 comments on commit 55007a0

Please sign in to comment.