feat: status page id validation and cache key
This commit is contained in:
@@ -2,6 +2,8 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/db"
|
||||
@@ -10,6 +12,26 @@ import (
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
// ErrInvalidPageID is returned for any page id that would not be safe or
|
||||
// pleasant in a URL handed to a customer.
|
||||
var ErrInvalidPageID = errors.New("page id must be 3-40 characters of a-z, 0-9 and -, starting and ending alphanumeric")
|
||||
|
||||
// The slug is operator-chosen rather than random because it is printed on
|
||||
// support pages and typed by people. First and last characters are
|
||||
// alphanumeric so a page id never reads as a flag or a trailing separator.
|
||||
var pageIDRe = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$`)
|
||||
|
||||
func ValidatePageID(id string) error {
|
||||
if !pageIDRe.MatchString(id) {
|
||||
return ErrInvalidPageID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func statusCacheKey(instanceID, pageID string) string {
|
||||
return "vantage:status:" + instanceID + ":" + pageID
|
||||
}
|
||||
|
||||
func spCtx() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), 5*time.Second)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package services
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A new instance-scoped collection that is not in ScopedCollections leaves its
|
||||
// rows behind when the instance is deleted. This is the cheapest possible
|
||||
@@ -20,3 +23,41 @@ func TestStatusCollectionsAreScoped(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePageID(t *testing.T) {
|
||||
valid := []string{"api", "prod-eu", "status2", "a1b", strings.Repeat("a", 40)}
|
||||
for _, s := range valid {
|
||||
if err := ValidatePageID(s); err != nil {
|
||||
t.Errorf("ValidatePageID(%q) = %v, want nil", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
invalid := []string{
|
||||
"", // empty
|
||||
"ab", // too short
|
||||
strings.Repeat("a", 41), // too long
|
||||
"-api", // leading hyphen
|
||||
"api-", // trailing hyphen
|
||||
"API", // uppercase
|
||||
"my page", // space
|
||||
"api_v2", // underscore
|
||||
"api/v2", // path separator
|
||||
"..", // dots
|
||||
}
|
||||
for _, s := range invalid {
|
||||
if err := ValidatePageID(s); err == nil {
|
||||
t.Errorf("ValidatePageID(%q) = nil, want error", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusCacheKeyIsScopedByInstance(t *testing.T) {
|
||||
a := statusCacheKey("inst-a", "api")
|
||||
b := statusCacheKey("inst-b", "api")
|
||||
if a == b {
|
||||
t.Fatalf("two instances share a cache key: %q", a)
|
||||
}
|
||||
if a != "vantage:status:inst-a:api" {
|
||||
t.Fatalf("statusCacheKey = %q, want vantage:status:inst-a:api", a)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user