NOISSUE - Add reports template tests (#264)

* fix template tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* add endpoint tests

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

* fix failing linter

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>

---------

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
Steve Munene
2025-08-06 11:18:36 +03:00
committed by GitHub
parent f4e3cfab6d
commit 8b4766d740
4 changed files with 951 additions and 1 deletions
+5
View File
@@ -180,6 +180,11 @@ jobs:
run: |
go test --race -v -count=1 -coverprofile=coverage/re.out ./re/...
- name: Run reports tests
if: steps.changes.outputs.reports == 'true' || steps.changes.outputs.workflow == 'true'
run: |
go test --race -v -count=1 -coverprofile=coverage/reports.out ./reports/...
- name: Run alarms tests
if: steps.changes.outputs.alarms == 'true' || steps.changes.outputs.workflow == 'true'
run: |
+569
View File
@@ -832,3 +832,572 @@ type respBody struct {
ID string `json:"id"`
Status reports.Status `json:"status"`
}
const (
validTemplate = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
<style>
body { font-family: Arial, sans-serif; }
.header { background-color: #f0f0f0; padding: 10px; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>{{$.Title}}</h1>
<p>Generated on: {{$.GeneratedDate}}</p>
</div>
<div class="content">
<h2>Messages</h2>
{{range .Messages}}
<div class="message">
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
</div>
{{end}}
</div>
</body>
</html>`
templateWithoutTitle = `<!DOCTYPE html>
<html>
<head>
<title>Report</title>
<style>
body { font-family: Arial, sans-serif; }
</style>
</head>
<body>
<h1>Report</h1>
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end}}
</body>
</html>`
templateWithSyntaxError = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end
</body>
</html>`
)
func TestUpdateReportTemplateEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
id string
template reports.ReportTemplate
domainID string
token string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcErr error
err error
}{
{
desc: "update report template successfully",
id: validID,
template: reports.ReportTemplate(validTemplate),
token: validToken,
contentType: contentType,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
status: http.StatusNoContent,
},
{
desc: "update report template with invalid token",
id: validID,
template: reports.ReportTemplate(validTemplate),
token: invalidToken,
authnRes: smqauthn.Session{},
domainID: domainID,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "update report template with empty token",
id: validID,
template: reports.ReportTemplate(validTemplate),
token: "",
authnRes: smqauthn.Session{},
domainID: domainID,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "update report template with empty domainID",
id: validID,
template: reports.ReportTemplate(validTemplate),
token: validToken,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "update report template with invalid content type",
id: validID,
template: reports.ReportTemplate(validTemplate),
token: validToken,
domainID: domainID,
contentType: "application/xml",
status: http.StatusUnsupportedMediaType,
err: apiutil.ErrUnsupportedContentType,
},
{
desc: "update report template with empty ID",
id: "",
template: reports.ReportTemplate(validTemplate),
token: validToken,
domainID: domainID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingID,
},
{
desc: "update report template with empty template",
id: validID,
token: validToken,
domainID: domainID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrValidation,
},
{
desc: "update report template without title field",
id: validID,
template: reports.ReportTemplate(templateWithoutTitle),
token: validToken,
domainID: domainID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrValidation,
},
{
desc: "update report template with syntax error",
id: validID,
template: reports.ReportTemplate(templateWithSyntaxError),
token: validToken,
domainID: domainID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrValidation,
},
{
desc: "update report template with service error",
id: validID,
template: reports.ReportTemplate(validTemplate),
token: validToken,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
contentType: contentType,
svcErr: svcerr.ErrUpdateEntity,
status: http.StatusUnprocessableEntity,
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
data := toJSON(map[string]interface{}{
"report_template": tc.template,
})
req := testRequest{
client: ts.Client(),
method: http.MethodPut,
url: fmt.Sprintf("%s/%s/reports/configs/%s/template", ts.URL, tc.domainID, tc.id),
contentType: tc.contentType,
token: tc.token,
body: strings.NewReader(data),
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("UpdateReportTemplate", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
if res.StatusCode != http.StatusNoContent {
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewReportTemplateEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
id string
domainID string
token string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcRes reports.ReportTemplate
svcErr error
err error
}{
{
desc: "view report template successfully",
id: validID,
token: validToken,
contentType: contentType,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
status: http.StatusOK,
svcRes: reports.ReportTemplate(validTemplate),
},
{
desc: "view report template with invalid token",
id: validID,
token: invalidToken,
authnRes: smqauthn.Session{},
domainID: domainID,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "view report template with empty token",
token: "",
authnRes: smqauthn.Session{},
domainID: domainID,
id: validID,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "view report template with empty domainID",
token: validToken,
id: validID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "view report template with empty ID",
token: validToken,
id: "",
domainID: domainID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingID,
},
{
desc: "view report template with service error",
token: validToken,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
id: validID,
contentType: contentType,
svcErr: svcerr.ErrViewEntity,
status: http.StatusBadRequest,
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodGet,
url: fmt.Sprintf("%s/%s/reports/configs/%s/template", ts.URL, tc.domainID, tc.id),
contentType: tc.contentType,
token: tc.token,
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("ViewReportTemplate", mock.Anything, tc.authnRes, tc.id).Return(tc.svcRes, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDeleteReportTemplateEndpoint(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
id string
domainID string
token string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcErr error
err error
}{
{
desc: "delete report template successfully",
id: validID,
token: validToken,
contentType: contentType,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
status: http.StatusNoContent,
},
{
desc: "delete report template with invalid token",
id: validID,
token: invalidToken,
authnRes: smqauthn.Session{},
domainID: domainID,
contentType: contentType,
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "delete report template with empty token",
token: "",
authnRes: smqauthn.Session{},
domainID: domainID,
id: validID,
contentType: contentType,
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
},
{
desc: "delete report template with empty domainID",
token: validToken,
id: validID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingDomainID,
},
{
desc: "delete report template with empty ID",
token: validToken,
id: "",
domainID: domainID,
contentType: contentType,
status: http.StatusBadRequest,
err: apiutil.ErrMissingID,
},
{
desc: "delete report template with service error",
token: validToken,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
id: validID,
contentType: contentType,
svcErr: svcerr.ErrRemoveEntity,
status: http.StatusUnprocessableEntity,
err: svcerr.ErrRemoveEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
req := testRequest{
client: ts.Client(),
method: http.MethodDelete,
url: fmt.Sprintf("%s/%s/reports/configs/%s/template", ts.URL, tc.domainID, tc.id),
contentType: tc.contentType,
token: tc.token,
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("DeleteReportTemplate", mock.Anything, tc.authnRes, tc.id).Return(tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
if res.StatusCode != http.StatusNoContent {
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
func TestGenerateReportWithTemplateValidation(t *testing.T) {
ts, svc, authn := newReportsServer()
defer ts.Close()
cases := []struct {
desc string
cfg reports.ReportConfig
action string
domainID string
token string
contentType string
status int
authnRes smqauthn.Session
authnErr error
svcRes reports.ReportPage
svcErr error
err error
}{
{
desc: "generate report with valid template successfully",
cfg: reports.ReportConfig{
ID: validID,
Name: namegen.Generate(),
DomainID: domainID,
Metrics: []reports.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
},
Config: &reports.MetricConfig{
From: "now()-1h",
To: "now()",
Title: title,
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
},
ReportTemplate: reports.ReportTemplate(validTemplate),
},
action: "view",
token: validToken,
contentType: contentType,
domainID: domainID,
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
status: http.StatusCreated,
svcRes: reports.ReportPage{},
},
{
desc: "generate report with invalid template",
cfg: reports.ReportConfig{
ID: validID,
Name: namegen.Generate(),
DomainID: domainID,
Metrics: []reports.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
},
Config: &reports.MetricConfig{
From: "now()-1h",
To: "now()",
Title: title,
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
},
ReportTemplate: reports.ReportTemplate(templateWithoutTitle),
},
action: "view",
token: validToken,
contentType: contentType,
domainID: domainID,
status: http.StatusBadRequest,
err: apiutil.ErrValidation,
},
{
desc: "generate report with template syntax error",
cfg: reports.ReportConfig{
ID: validID,
Name: namegen.Generate(),
DomainID: domainID,
Metrics: []reports.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
},
Config: &reports.MetricConfig{
From: "now()-1h",
To: "now()",
Title: title,
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
},
ReportTemplate: reports.ReportTemplate(templateWithSyntaxError),
},
action: "view",
token: validToken,
contentType: contentType,
domainID: domainID,
status: http.StatusBadRequest,
err: apiutil.ErrValidation,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
data := toJSON(tc.cfg)
req := testRequest{
client: ts.Client(),
method: http.MethodPost,
url: fmt.Sprintf("%s/%s/reports?action=%s", ts.URL, tc.domainID, tc.action),
contentType: tc.contentType,
token: tc.token,
body: strings.NewReader(data),
}
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
svcCall := svc.On("GenerateReport", mock.Anything, tc.authnRes, mock.Anything, mock.Anything).Return(tc.svcRes, tc.svcErr)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
var errRes respBody
err = json.NewDecoder(res.Body).Decode(&errRes)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
if errRes.Err != "" || errRes.Message != "" {
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
}
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
authCall.Unset()
})
}
}
+1 -1
View File
@@ -198,7 +198,7 @@ func (req updateReportTemplateReq) validate() error {
return apiutil.ErrMissingID
}
if req.ReportTemplate == "" {
return errors.Wrap(apiutil.ErrValidation, errMissingReportTemplate)
return errors.Wrap(errMissingReportTemplate, apiutil.ErrValidation)
}
if err := req.ReportTemplate.Validate(); err != nil {
return errors.Wrap(err, apiutil.ErrValidation)
+376
View File
@@ -0,0 +1,376 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package reports_test
import (
"fmt"
"testing"
"github.com/absmach/magistrala/reports"
"github.com/stretchr/testify/assert"
)
const (
validTemplate = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
<style>
body { font-family: Arial, sans-serif; }
.header { background-color: #f0f0f0; padding: 10px; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>{{$.Title}}</h1>
<p>Generated on: {{$.GeneratedDate}}</p>
</div>
<div class="content">
<h2>Messages</h2>
{{range .Messages}}
<div class="message">
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
</div>
{{end}}
</div>
</body>
</html>`
templateWithoutTitle = `<!DOCTYPE html>
<html>
<head>
<title>Report</title>
<style>
body { font-family: Arial, sans-serif; }
</style>
</head>
<body>
<h1>Report</h1>
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end}}
</body>
</html>`
templateWithoutRange = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
<p>No messages to display</p>
</body>
</html>`
templateWithoutFormatTime = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{range .Messages}}
<p>Time: {{.Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end}}
</body>
</html>`
templateWithoutFormatValue = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{.}}</p>
{{end}}
</body>
</html>`
templateWithoutEnd = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
<p>Time: {{formatTime "test"}}</p>
<p>Value: {{formatValue "test"}}</p>
<p>No range block with end</p>
</body>
</html>`
templateWithSyntaxError = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end
</body>
</html>`
templateWithUndefinedFunction = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
<p>Custom: {{customFunction .}}</p>
{{end}}
</body>
</html>`
templateWithIfCondition = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{if .Messages}}
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end}}
{{else}}
<p>No messages available</p>
{{end}}
</body>
</html>`
templateWithWithCondition = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{with .Data}}
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end}}
{{else}}
<p>No data available</p>
{{end}}
</body>
</html>`
templateWithNestedConditions = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{if .HasMessages}}
{{with .Data}}
{{range .Messages}}
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end}}
{{else}}
<p>Data not available</p>
{{end}}
{{else}}
<p>No messages flag set</p>
{{end}}
</body>
</html>`
templateWithIfMissingFields = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{if .Messages}}
{{range .Messages}}
<p>Time: {{.Time}}</p>
<p>Value: {{.}}</p>
{{end}}
{{else}}
<p>No messages available</p>
{{end}}
</body>
</html>`
templateWithWithMissingFields = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
</head>
<body>
<h1>{{$.Title}}</h1>
{{with .Data}}
{{range .Messages}}
<p>Time: {{.Time}}</p>
<p>Value: {{formatValue .}}</p>
{{end}}
{{else}}
<p>No data available</p>
{{end}}
</body>
</html>`
)
func TestReportTemplate_Validate(t *testing.T) {
cases := []struct {
desc string
template reports.ReportTemplate
err error
}{
{
desc: "validate template successfully",
template: reports.ReportTemplate(validTemplate),
err: nil,
},
{
desc: "validate template without title field",
template: reports.ReportTemplate(templateWithoutTitle),
err: fmt.Errorf("missing essential template field: {{$.Title}}"),
},
{
desc: "validate template without range field",
template: reports.ReportTemplate(templateWithoutRange),
err: fmt.Errorf("missing essential template field: {{range .Messages}}"),
},
{
desc: "validate template without formatTime field",
template: reports.ReportTemplate(templateWithoutFormatTime),
err: fmt.Errorf("missing essential template field: {{formatTime .Time}}"),
},
{
desc: "validate template without formatValue field",
template: reports.ReportTemplate(templateWithoutFormatValue),
err: fmt.Errorf("missing essential template field: {{formatValue .}}"),
},
{
desc: "validate template without end field",
template: reports.ReportTemplate(templateWithoutEnd),
err: fmt.Errorf("missing essential template field: {{range .Messages}}"),
},
{
desc: "validate template with syntax error",
template: reports.ReportTemplate(templateWithSyntaxError),
err: fmt.Errorf("template syntax error"),
},
{
desc: "validate template with undefined function",
template: reports.ReportTemplate(templateWithUndefinedFunction),
err: fmt.Errorf("template syntax error"),
},
{
desc: "validate empty template",
template: reports.ReportTemplate(""),
err: fmt.Errorf("missing essential template field: {{$.Title}}"),
},
{
desc: "validate template with if condition successfully",
template: reports.ReportTemplate(templateWithIfCondition),
err: nil,
},
{
desc: "validate template `with` with condition successfully",
template: reports.ReportTemplate(templateWithWithCondition),
err: nil,
},
{
desc: "validate template with nested conditions successfully",
template: reports.ReportTemplate(templateWithNestedConditions),
err: nil,
},
{
desc: "validate template with if condition missing formatTime",
template: reports.ReportTemplate(templateWithIfMissingFields),
err: fmt.Errorf("missing essential template field: {{formatTime .Time}}"),
},
{
desc: "validate template `with` with condition missing formatTime",
template: reports.ReportTemplate(templateWithWithMissingFields),
err: fmt.Errorf("missing essential template field: {{formatTime .Time}}"),
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
err := tc.template.Validate()
if tc.err != nil {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.err.Error())
} else {
assert.NoError(t, err)
}
})
}
}
func TestReportTemplate_String(t *testing.T) {
template := reports.ReportTemplate(validTemplate)
result := template.String()
assert.Equal(t, validTemplate, result)
}
func TestReportTemplate_MarshalJSON(t *testing.T) {
template := reports.ReportTemplate("simple template")
data, err := template.MarshalJSON()
assert.NoError(t, err)
assert.NotNil(t, data)
assert.Equal(t, `"simple template"`, string(data))
}
func TestReportTemplate_UnmarshalJSON(t *testing.T) {
cases := []struct {
desc string
data []byte
expected string
err error
}{
{
desc: "unmarshal valid JSON successfully",
data: []byte(`"simple template"`),
expected: "simple template",
err: nil,
},
{
desc: "unmarshal invalid JSON",
data: []byte(`invalid json`),
err: fmt.Errorf("invalid character"),
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var template reports.ReportTemplate
err := template.UnmarshalJSON(tc.data)
if tc.err != nil {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.err.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, tc.expected, string(template))
}
})
}
}