console: API validation for /graph/table-interval

Also add simple HTTP tests.
This commit is contained in:
Vincent Bernat
2024-03-25 14:49:48 +01:00
parent e41e64a3ac
commit 4eaaf977b7
4 changed files with 66 additions and 30 deletions

View File

@@ -13,15 +13,9 @@ import (
"text/template"
"time"
"github.com/gin-gonic/gin"
"net/http"
"akvorado/common/helpers"
"akvorado/console/query"
)
const defaultPointsNumber = 200
// flowsTable describe a consolidated or unconsolidated flows table.
type flowsTable struct {
Name string
@@ -123,11 +117,6 @@ type context struct {
ToStartOfInterval func(string) string
}
type tableIntervalResult struct {
Table string `json:"table"`
Interval uint64 `json:"interval"`
}
// templateEscape escapes `{{` and `}}` from a string. In fact, only
// the opening tag needs to be escaped.
func templateEscape(input string) string {
@@ -151,17 +140,6 @@ func templateContext(context inputContext) string {
return fmt.Sprintf("context `%s`", string(encoded))
}
func (c *Component) getTableAndIntervalHandlerFunc(gc *gin.Context) {
input := inputContext{Points: defaultPointsNumber}
if err := gc.ShouldBindJSON(&input); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return
}
table, interval, _ := c.computeTableAndInterval(input)
gc.JSON(http.StatusOK, tableIntervalResult{Table: table, Interval: uint64(interval.Seconds())})
}
func (c *Component) contextFunc(inputStr string) context {
var input inputContext
if err := json.Unmarshal([]byte(inputStr), &input); err != nil {

View File

@@ -301,12 +301,12 @@ func TestFinalizeQuery(t *testing.T) {
}
}
func TestGetTableInterval(t *testing.T) {
func TestComputeBestTableAndInterval(t *testing.T) {
cases := []struct {
Description string
Tables []flowsTable
Context inputContext
Expected tableIntervalResult
Expected tableIntervalOutput
}{
{
Description: "simple query without additional tables",
@@ -315,7 +315,7 @@ func TestGetTableInterval(t *testing.T) {
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 86400,
},
Expected: tableIntervalResult{Table: "flows", Interval: 1},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "query with main table",
Context: inputContext{
@@ -324,7 +324,7 @@ func TestGetTableInterval(t *testing.T) {
MainTableRequired: true,
Points: 86400,
},
Expected: tableIntervalResult{Table: "flows", Interval: 1},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "only flows table available",
Tables: []flowsTable{{"flows", 0, time.Date(2022, 3, 10, 15, 45, 10, 0, time.UTC)}},
@@ -333,7 +333,7 @@ func TestGetTableInterval(t *testing.T) {
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 86400,
},
Expected: tableIntervalResult{Table: "flows", Interval: 1},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "select flows table out of range",
Tables: []flowsTable{
@@ -345,7 +345,7 @@ func TestGetTableInterval(t *testing.T) {
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution,
},
Expected: tableIntervalResult{Table: "flows", Interval: 1},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "select consolidated table with better resolution",
Tables: []flowsTable{
@@ -358,7 +358,7 @@ func TestGetTableInterval(t *testing.T) {
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution,
},
Expected: tableIntervalResult{Table: "flows_1m0s", Interval: 60},
Expected: tableIntervalOutput{Table: "flows_1m0s", Interval: 60},
},
}
@@ -368,7 +368,7 @@ func TestGetTableInterval(t *testing.T) {
c.flowsTables = tc.Tables
table, interval, _ := c.computeTableAndInterval(
tc.Context)
got := tableIntervalResult{
got := tableIntervalOutput{
Table: table,
Interval: uint64(interval.Seconds()),
}

View File

@@ -422,3 +422,29 @@ func (c *Component) graphLineHandlerFunc(gc *gin.Context) {
}
gc.JSON(http.StatusOK, output)
}
type tableIntervalInput struct {
Start time.Time `json:"start" binding:"required"`
End time.Time `json:"end" binding:"required,gtfield=Start"`
Points uint `json:"points" binding:"required,min=5,max=2000"` // minimum number of points
}
type tableIntervalOutput struct {
Table string `json:"table"`
Interval uint64 `json:"interval"`
}
func (c *Component) getTableAndIntervalHandlerFunc(gc *gin.Context) {
var input tableIntervalInput
if err := gc.ShouldBindJSON(&input); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return
}
table, interval, _ := c.computeTableAndInterval(inputContext{
Points: input.Points,
Start: input.Start,
End: input.End,
})
gc.JSON(http.StatusOK, tableIntervalOutput{Table: table, Interval: uint64(interval.Seconds())})
}

View File

@@ -979,3 +979,35 @@ func TestGraphLineHandler(t *testing.T) {
},
})
}
func TestGetTableInterval(t *testing.T) {
_, h, _, mockClock := NewMock(t, DefaultConfiguration())
mockClock.Set(time.Date(2022, 4, 12, 15, 45, 10, 0, time.UTC))
helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{
{
Description: "simple query",
URL: "/api/v0/console/graph/table-interval",
JSONInput: gin.H{
"start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
"end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
"points": 300,
},
JSONOutput: gin.H{
"table": "flows",
"interval": 1,
},
}, {
Description: "too many points",
URL: "/api/v0/console/graph/table-interval",
JSONInput: gin.H{
"start": time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
"end": time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
"points": 86400,
},
StatusCode: 400,
JSONOutput: gin.H{
"message": `Key: 'tableIntervalInput.Points' Error:Field validation for 'Points' failed on the 'max' tag`,
},
},
})
}