Implement checker options retrieval
This commit is contained in:
parent
3536c712a9
commit
40141120d2
12 changed files with 1051 additions and 6 deletions
|
|
@ -55,6 +55,22 @@ func (uc *CheckerController) CheckerHandler(c *gin.Context) {
|
|||
c.Next()
|
||||
}
|
||||
|
||||
// CheckerOptionHandler is a middleware that retrieves a specific option and sets it in the context.
|
||||
func (uc *CheckerController) CheckerOptionHandler(c *gin.Context) {
|
||||
cname := c.Param("cname")
|
||||
optname := c.Param("optname")
|
||||
|
||||
opts, err := uc.checkerService.GetCheckerOptions(cname, nil, nil, nil)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("option", (*opts)[optname])
|
||||
|
||||
c.Next()
|
||||
}
|
||||
|
||||
// ListCheckers retrieves all available checks.
|
||||
//
|
||||
// @Summary List checkers (admin)
|
||||
|
|
@ -109,3 +125,135 @@ func (uc *CheckerController) GetCheckerStatus(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
||||
// GetCheckerOptions retrieves all options for a check.
|
||||
//
|
||||
// @Summary Get check options (admin)
|
||||
// @Schemes
|
||||
// @Description Retrieves all configuration options for a specific check.
|
||||
// @Tags checks
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param cname path string true "Checker name"
|
||||
// @Success 200 {object} happydns.CheckerOptions "Checker options as key-value pairs"
|
||||
// @Failure 404 {object} happydns.ErrorResponse "Checker not found"
|
||||
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
|
||||
// @Router /checks/{cname}/options [get]
|
||||
func (uc *CheckerController) GetCheckerOptions(c *gin.Context) {
|
||||
cname := c.Param("cname")
|
||||
|
||||
opts, err := uc.checkerService.GetCheckerOptions(cname, nil, nil, nil)
|
||||
happydns.ApiResponse(c, opts, err)
|
||||
}
|
||||
|
||||
// AddCheckerOptions adds or overwrites specific admin-level options for a check.
|
||||
//
|
||||
// @Summary Add checker options
|
||||
// @Schemes
|
||||
// @Description Adds or overwrites specific configuration options for a checker without affecting other options.
|
||||
// @Tags checks
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param cname path string true "Checker name"
|
||||
// @Param body body happydns.SetCheckerOptionsRequest true "Options to add or overwrite"
|
||||
// @Success 200 {object} bool "Success status"
|
||||
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
|
||||
// @Failure 404 {object} happydns.ErrorResponse "Checker not found"
|
||||
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
|
||||
// @Router /checks/{cname}/options [post]
|
||||
func (uc *CheckerController) AddCheckerOptions(c *gin.Context) {
|
||||
cname := c.Param("cname")
|
||||
|
||||
var req happydns.SetCheckerOptionsRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
middleware.ErrorResponse(c, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = uc.checkerService.OverwriteSomeCheckerOptions(cname, nil, nil, nil, req.Options)
|
||||
happydns.ApiResponse(c, true, err)
|
||||
}
|
||||
|
||||
// ChangeCheckerOptions replaces all options for a checker.
|
||||
//
|
||||
// @Summary Replace checker options (admin)
|
||||
// @Schemes
|
||||
// @Description Replaces all configuration options for a check with the provided options.
|
||||
// @Tags checks
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param cname path string true "Checker name"
|
||||
// @Param body body happydns.SetCheckerOptionsRequest true "New complete set of options"
|
||||
// @Success 200 {object} bool "Success status"
|
||||
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
|
||||
// @Failure 404 {object} happydns.ErrorResponse "Checker not found"
|
||||
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
|
||||
// @Router /checks/{cname}/options [put]
|
||||
func (uc *CheckerController) ChangeCheckerOptions(c *gin.Context) {
|
||||
cname := c.Param("cname")
|
||||
|
||||
var req happydns.SetCheckerOptionsRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
middleware.ErrorResponse(c, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = uc.checkerService.SetCheckerOptions(cname, nil, nil, nil, req.Options)
|
||||
happydns.ApiResponse(c, true, err)
|
||||
}
|
||||
|
||||
// GetCheckerOption retrieves a specific option value for a checker.
|
||||
//
|
||||
// @Summary Get checker option (admin)
|
||||
// @Schemes
|
||||
// @Description Retrieves the value of a specific configuration option for a checker.
|
||||
// @Tags checks
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param cname path string true "Checker name"
|
||||
// @Param optname path string true "Option name"
|
||||
// @Success 200 {object} object "Option value (type varies)"
|
||||
// @Failure 404 {object} happydns.ErrorResponse "Checker not found"
|
||||
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
|
||||
// @Router /checks/{cname}/options/{optname} [get]
|
||||
func (uc *CheckerController) GetCheckerOption(c *gin.Context) {
|
||||
opt := c.MustGet("option")
|
||||
|
||||
happydns.ApiResponse(c, opt, nil)
|
||||
}
|
||||
|
||||
// SetCheckerOption sets or updates a specific option value for a checker.
|
||||
//
|
||||
// @Summary Set checker option (admin)
|
||||
// @Schemes
|
||||
// @Description Sets or updates the value of a specific configuration option for a checker.
|
||||
// @Tags checks
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param cname path string true "Checker name"
|
||||
// @Param optname path string true "Option name"
|
||||
// @Param body body object true "Option value (type varies by option)"
|
||||
// @Success 200 {object} bool "Success status"
|
||||
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
|
||||
// @Failure 404 {object} happydns.ErrorResponse "Checker not found"
|
||||
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
|
||||
// @Router /checks/{cname}/options/{optname} [put]
|
||||
func (uc *CheckerController) SetCheckerOption(c *gin.Context) {
|
||||
cname := c.Param("cname")
|
||||
optname := c.Param("optname")
|
||||
|
||||
var req any
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
middleware.ErrorResponse(c, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
po := happydns.CheckerOptions{}
|
||||
po[optname] = req
|
||||
|
||||
err = uc.checkerService.OverwriteSomeCheckerOptions(cname, nil, nil, nil, po)
|
||||
happydns.ApiResponse(c, true, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,4 +39,13 @@ func declareChecksRoutes(router *gin.RouterGroup, dep Dependencies) {
|
|||
|
||||
apiCheckerRoutes.GET("", cc.GetCheckerStatus)
|
||||
//apiCheckerRoutes.POST("", tpc.ChangeCheckerStatus)
|
||||
|
||||
apiCheckerRoutes.GET("/options", cc.GetCheckerOptions)
|
||||
apiCheckerRoutes.POST("/options", cc.AddCheckerOptions)
|
||||
apiCheckerRoutes.PUT("/options", cc.ChangeCheckerOptions)
|
||||
|
||||
apiCheckerOptionsRoutes := apiCheckerRoutes.Group("/options/:optname")
|
||||
apiCheckerOptionsRoutes.Use(cc.CheckerOptionHandler)
|
||||
apiCheckerOptionsRoutes.GET("", cc.GetCheckerOption)
|
||||
apiCheckerOptionsRoutes.PUT("", cc.SetCheckerOption)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ func (app *App) initUsecases() {
|
|||
app.usecases.authUser = authUserService
|
||||
app.usecases.resolver = usecase.NewResolverUsecase(app.cfg)
|
||||
app.usecases.session = sessionService
|
||||
app.usecases.checker = checkUC.NewCheckerUsecase(app.cfg)
|
||||
app.usecases.checker = checkUC.NewCheckerUsecase(app.cfg, app.store)
|
||||
|
||||
app.usecases.orchestrator = orchestrator.NewOrchestrator(
|
||||
domainLogService,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ type InMemoryStorage struct {
|
|||
data map[string][]byte // Generic key-value store for KVStorage interface
|
||||
authUsers map[string]*happydns.UserAuth
|
||||
authUsersByEmail map[string]happydns.Identifier
|
||||
checksCfg map[string]*happydns.CheckerOptions
|
||||
domains map[string]*happydns.Domain
|
||||
domainLogs map[string]*happydns.DomainLogWithDomainId
|
||||
domainLogsByDomains map[string][]*happydns.Identifier
|
||||
|
|
@ -58,6 +59,7 @@ func NewInMemoryStorage() (*InMemoryStorage, error) {
|
|||
data: make(map[string][]byte),
|
||||
authUsers: make(map[string]*happydns.UserAuth),
|
||||
authUsersByEmail: make(map[string]happydns.Identifier),
|
||||
checksCfg: make(map[string]*happydns.CheckerOptions),
|
||||
domains: make(map[string]*happydns.Domain),
|
||||
domainLogs: make(map[string]*happydns.DomainLogWithDomainId),
|
||||
domainLogsByDomains: make(map[string][]*happydns.Identifier),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ package storage // import "git.happydns.org/happyDomain/internal/storage"
|
|||
|
||||
import (
|
||||
"git.happydns.org/happyDomain/internal/usecase/authuser"
|
||||
"git.happydns.org/happyDomain/internal/usecase/check"
|
||||
"git.happydns.org/happyDomain/internal/usecase/domain"
|
||||
"git.happydns.org/happyDomain/internal/usecase/domain_log"
|
||||
"git.happydns.org/happyDomain/internal/usecase/insight"
|
||||
|
|
@ -43,6 +44,7 @@ type Storage interface {
|
|||
domain.DomainStorage
|
||||
domainlog.DomainLogStorage
|
||||
insight.InsightStorage
|
||||
check.CheckerStorage
|
||||
provider.ProviderStorage
|
||||
session.SessionStorage
|
||||
user.UserStorage
|
||||
|
|
|
|||
185
internal/storage/kvtpl/check.go
Normal file
185
internal/storage/kvtpl/check.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
func (s *KVStorage) ListAllCheckerConfigurations() (happydns.Iterator[happydns.CheckerOptions], error) {
|
||||
iter := s.db.Search("chckrcfg-")
|
||||
return NewKVIterator[happydns.CheckerOptions](s.db, iter), nil
|
||||
}
|
||||
|
||||
func buildCheckerKey(cname string, user *happydns.Identifier, domain *happydns.Identifier, service *happydns.Identifier) string {
|
||||
u := ""
|
||||
if user != nil {
|
||||
u = user.String()
|
||||
}
|
||||
|
||||
d := ""
|
||||
if domain != nil {
|
||||
d = domain.String()
|
||||
}
|
||||
|
||||
s := ""
|
||||
if service != nil {
|
||||
s = service.String()
|
||||
}
|
||||
|
||||
return strings.Join([]string{cname, u, d, s}, "/")
|
||||
}
|
||||
|
||||
func keyToPositional(key string, opts *happydns.CheckerOptions) (*happydns.CheckerOptionsPositional, error) {
|
||||
tmp := strings.Split(key, "/")
|
||||
|
||||
if len(tmp) < 4 {
|
||||
return nil, fmt.Errorf("malformed plugin configuration key, got %q", key)
|
||||
}
|
||||
|
||||
cname := tmp[0]
|
||||
|
||||
var userid *happydns.Identifier
|
||||
if len(tmp[1]) > 0 {
|
||||
u, err := happydns.NewIdentifierFromString(tmp[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userid = &u
|
||||
}
|
||||
|
||||
var domainid *happydns.Identifier
|
||||
if len(tmp[2]) > 0 {
|
||||
d, err := happydns.NewIdentifierFromString(tmp[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainid = &d
|
||||
}
|
||||
|
||||
var serviceid *happydns.Identifier
|
||||
if len(tmp[3]) > 0 {
|
||||
s, err := happydns.NewIdentifierFromString(tmp[3])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serviceid = &s
|
||||
}
|
||||
|
||||
return &happydns.CheckerOptionsPositional{
|
||||
CheckName: cname,
|
||||
UserId: userid,
|
||||
DomainId: domainid,
|
||||
ServiceId: serviceid,
|
||||
Options: *opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *KVStorage) ListCheckerConfiguration(cname string) (configs []*happydns.CheckerOptionsPositional, err error) {
|
||||
iter := s.db.Search("chckrcfg-" + cname + "/")
|
||||
defer iter.Release()
|
||||
|
||||
for iter.Next() {
|
||||
var p happydns.CheckerOptions
|
||||
|
||||
e := s.db.DecodeData(iter.Value(), &p)
|
||||
if e != nil {
|
||||
err = errors.Join(err, e)
|
||||
continue
|
||||
}
|
||||
|
||||
opts, e := keyToPositional(strings.TrimPrefix(iter.Key(), "chckrcfg-"), &p)
|
||||
if e != nil {
|
||||
err = errors.Join(err, e)
|
||||
continue
|
||||
}
|
||||
|
||||
configs = append(configs, opts)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *KVStorage) GetCheckerConfiguration(cname string, user *happydns.Identifier, domain *happydns.Identifier, service *happydns.Identifier) (configs []*happydns.CheckerOptionsPositional, err error) {
|
||||
iter := s.db.Search("chckrcfg-" + cname + "/")
|
||||
defer iter.Release()
|
||||
|
||||
for iter.Next() {
|
||||
var p happydns.CheckerOptions
|
||||
|
||||
e := s.db.DecodeData(iter.Value(), &p)
|
||||
if e != nil {
|
||||
err = errors.Join(err, e)
|
||||
continue
|
||||
}
|
||||
|
||||
opts, e := keyToPositional(strings.TrimPrefix(iter.Key(), "chckrcfg-"), &p)
|
||||
if e != nil {
|
||||
err = errors.Join(err, e)
|
||||
continue
|
||||
}
|
||||
|
||||
// Match logic:
|
||||
// - When parameter is nil: match ONLY configs with nil ID (requesting specific scope)
|
||||
// - When parameter is not nil: match configs with nil ID (admin-level) OR matching ID
|
||||
matchUser := (user == nil && opts.UserId == nil) ||
|
||||
(user != nil && (opts.UserId == nil || opts.UserId.Equals(*user)))
|
||||
|
||||
matchDomain := (domain == nil && opts.DomainId == nil) ||
|
||||
(domain != nil && (opts.DomainId == nil || opts.DomainId.Equals(*domain)))
|
||||
|
||||
matchService := (service == nil && opts.ServiceId == nil) ||
|
||||
(service != nil && (opts.ServiceId == nil || opts.ServiceId.Equals(*service)))
|
||||
|
||||
if matchUser && matchDomain && matchService {
|
||||
configs = append(configs, opts)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *KVStorage) UpdateCheckerConfiguration(cname string, user *happydns.Identifier, domain *happydns.Identifier, service *happydns.Identifier, opts happydns.CheckerOptions) error {
|
||||
return s.db.Put(fmt.Sprintf("chckrcfg-%s", buildCheckerKey(cname, user, domain, service)), opts)
|
||||
}
|
||||
|
||||
func (s *KVStorage) DeleteCheckerConfiguration(cname string, user *happydns.Identifier, domain *happydns.Identifier, service *happydns.Identifier) error {
|
||||
return s.db.Delete(fmt.Sprintf("chckrcfg-%s", buildCheckerKey(cname, user, domain, service)))
|
||||
}
|
||||
|
||||
func (s *KVStorage) ClearCheckerConfigurations() error {
|
||||
iter := s.db.Search("chckrcfg-")
|
||||
defer iter.Release()
|
||||
|
||||
for iter.Next() {
|
||||
err := s.db.Delete(iter.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
450
internal/usecase/check/check_options_test.go
Normal file
450
internal/usecase/check/check_options_test.go
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2026 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package check_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.happydns.org/happyDomain/checks"
|
||||
"git.happydns.org/happyDomain/internal/storage"
|
||||
"git.happydns.org/happyDomain/internal/storage/inmemory"
|
||||
kv "git.happydns.org/happyDomain/internal/storage/kvtpl"
|
||||
uc "git.happydns.org/happyDomain/internal/usecase/check"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// mockCheckerForOptions – registered once at package init.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const testCheckerName = "test-mock-checker-options"
|
||||
|
||||
type mockCheckerForOptions struct{}
|
||||
|
||||
func (m *mockCheckerForOptions) ID() string { return testCheckerName }
|
||||
func (m *mockCheckerForOptions) Name() string { return testCheckerName }
|
||||
func (m *mockCheckerForOptions) Availability() happydns.CheckerAvailability {
|
||||
return happydns.CheckerAvailability{ApplyToDomain: true}
|
||||
}
|
||||
func (m *mockCheckerForOptions) Options() happydns.CheckerOptionsDocumentation {
|
||||
return happydns.CheckerOptionsDocumentation{
|
||||
RunOpts: []happydns.CheckerOptionDocumentation{
|
||||
{Id: "run-param", Default: "run-default"},
|
||||
},
|
||||
DomainOpts: []happydns.CheckerOptionDocumentation{
|
||||
{Id: "domain-autofill", AutoFill: happydns.AutoFillDomainName},
|
||||
{Id: "domain-param", Default: "domain-default"},
|
||||
},
|
||||
UserOpts: []happydns.CheckerOptionDocumentation{
|
||||
{Id: "user-param"},
|
||||
},
|
||||
ServiceOpts: []happydns.CheckerOptionDocumentation{
|
||||
{Id: "service-param"},
|
||||
},
|
||||
}
|
||||
}
|
||||
func (m *mockCheckerForOptions) RunCheck(_ context.Context, opts happydns.CheckerOptions, meta map[string]string) (*happydns.CheckResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
checks.RegisterChecker(testCheckerName, &mockCheckerForOptions{})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: create a fresh in-memory database for each test.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func newOptionsTestDB(t *testing.T) storage.Storage {
|
||||
t.Helper()
|
||||
mem, err := inmemory.NewInMemoryStorage()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create in-memory storage: %v", err)
|
||||
}
|
||||
db, err := kv.NewKVDatabase(mem)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create KV database: %v", err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func newTestCheckerUsecase(db storage.Storage) happydns.CheckerUsecase {
|
||||
return uc.NewCheckerUsecase(&happydns.Options{}, db, db)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GetStoredCheckerOptionsNoDefault tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Test_GetStoredOptions_EmptyStore(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
opts, err := checkerUC.GetStoredCheckerOptionsNoDefault(testCheckerName, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(opts) != 0 {
|
||||
t.Errorf("expected empty options from empty store, got %v", opts)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetStoredOptions_MergesStored(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
userId, _ := happydns.NewRandomIdentifier()
|
||||
// Store user-level option.
|
||||
if err := db.UpdateCheckerConfiguration(testCheckerName, &userId, nil, nil, happydns.CheckerOptions{"user-param": "val"}); err != nil {
|
||||
t.Fatalf("failed to seed option: %v", err)
|
||||
}
|
||||
|
||||
opts, err := checkerUC.GetStoredCheckerOptionsNoDefault(testCheckerName, &userId, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if opts["user-param"] != "val" {
|
||||
t.Errorf("expected user-param='val', got %v", opts["user-param"])
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetStoredOptions_AutoFillInjects(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
// Create a domain in the db.
|
||||
domain := &happydns.Domain{
|
||||
DomainName: "example.com.",
|
||||
}
|
||||
if err := db.CreateDomain(domain); err != nil {
|
||||
t.Fatalf("failed to create domain: %v", err)
|
||||
}
|
||||
|
||||
opts, err := checkerUC.GetStoredCheckerOptionsNoDefault(testCheckerName, nil, &domain.Id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if opts["domain-autofill"] != "example.com." {
|
||||
t.Errorf("expected domain-autofill='example.com.', got %v", opts["domain-autofill"])
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetStoredOptions_UnknownCheckerReturnsStored(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
// Store options for an unknown checker.
|
||||
if err := db.UpdateCheckerConfiguration("unknown-checker", nil, nil, nil, happydns.CheckerOptions{"some-param": "some-value"}); err != nil {
|
||||
t.Fatalf("failed to seed option: %v", err)
|
||||
}
|
||||
|
||||
opts, err := checkerUC.GetStoredCheckerOptionsNoDefault("unknown-checker", nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if opts["some-param"] != "some-value" {
|
||||
t.Errorf("expected some-param='some-value', got %v", opts["some-param"])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BuildMergedCheckerOptions tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Test_BuildMerged_DefaultsFirst(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
merged, err := checkerUC.BuildMergedCheckerOptions(testCheckerName, nil, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if merged["run-param"] != "run-default" {
|
||||
t.Errorf("expected run-param='run-default', got %v", merged["run-param"])
|
||||
}
|
||||
if merged["domain-param"] != "domain-default" {
|
||||
t.Errorf("expected domain-param='domain-default', got %v", merged["domain-param"])
|
||||
}
|
||||
}
|
||||
|
||||
func Test_BuildMerged_StoredOverridesDefault(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
domainId, _ := happydns.NewRandomIdentifier()
|
||||
|
||||
// Store domain-level option that overrides the default.
|
||||
if err := db.UpdateCheckerConfiguration(testCheckerName, nil, &domainId, nil, happydns.CheckerOptions{"domain-param": "custom"}); err != nil {
|
||||
t.Fatalf("failed to seed option: %v", err)
|
||||
}
|
||||
|
||||
merged, err := checkerUC.BuildMergedCheckerOptions(testCheckerName, nil, &domainId, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if merged["domain-param"] != "custom" {
|
||||
t.Errorf("expected domain-param='custom' (stored overrides default), got %v", merged["domain-param"])
|
||||
}
|
||||
}
|
||||
|
||||
func Test_BuildMerged_RunOptsOverrideStored(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
// Store an admin-level value for run-param.
|
||||
if err := db.UpdateCheckerConfiguration(testCheckerName, nil, nil, nil, happydns.CheckerOptions{"run-param": "stored-value"}); err != nil {
|
||||
t.Fatalf("failed to seed option: %v", err)
|
||||
}
|
||||
|
||||
runOpts := happydns.CheckerOptions{"run-param": "runtime"}
|
||||
merged, err := checkerUC.BuildMergedCheckerOptions(testCheckerName, nil, nil, nil, runOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if merged["run-param"] != "runtime" {
|
||||
t.Errorf("expected run-param='runtime' (runOpts wins), got %v", merged["run-param"])
|
||||
}
|
||||
}
|
||||
|
||||
func Test_BuildMerged_AutoFillWinsOverAll(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
// Create domain in db.
|
||||
domain := &happydns.Domain{
|
||||
DomainName: "example.com.",
|
||||
}
|
||||
if err := db.CreateDomain(domain); err != nil {
|
||||
t.Fatalf("failed to create domain: %v", err)
|
||||
}
|
||||
|
||||
// Both stored and runOpts attempt to set domain-autofill.
|
||||
if err := db.UpdateCheckerConfiguration(testCheckerName, nil, nil, nil, happydns.CheckerOptions{"domain-autofill": "manual-value"}); err != nil {
|
||||
t.Fatalf("failed to seed option: %v", err)
|
||||
}
|
||||
runOpts := happydns.CheckerOptions{"domain-autofill": "runtime-value"}
|
||||
|
||||
merged, err := checkerUC.BuildMergedCheckerOptions(testCheckerName, nil, &domain.Id, nil, runOpts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// Auto-fill always wins.
|
||||
if merged["domain-autofill"] != "example.com." {
|
||||
t.Errorf("expected domain-autofill='example.com.' (auto-fill wins), got %v", merged["domain-autofill"])
|
||||
}
|
||||
}
|
||||
|
||||
func Test_BuildMerged_NilAutoFillStoreSkips(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
// Pass nil as the CheckAutoFillStorage interface (not a typed nil).
|
||||
checkerUC := uc.NewCheckerUsecase(&happydns.Options{}, db, nil)
|
||||
|
||||
domainId, _ := happydns.NewRandomIdentifier()
|
||||
|
||||
// Should not panic even when autoFillStore is nil.
|
||||
merged, err := checkerUC.BuildMergedCheckerOptions(testCheckerName, nil, &domainId, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// domain-autofill should NOT be set (no auto-fill storage available).
|
||||
if _, ok := merged["domain-autofill"]; ok {
|
||||
t.Errorf("expected domain-autofill to be absent when autoFillStore is nil, got %v", merged["domain-autofill"])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SetCheckerOptions tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Test_SetOptions_ServiceLevel(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
userId, _ := happydns.NewRandomIdentifier()
|
||||
domainId, _ := happydns.NewRandomIdentifier()
|
||||
serviceId, _ := happydns.NewRandomIdentifier()
|
||||
|
||||
opts := happydns.CheckerOptions{"service-param": "val"}
|
||||
if err := checkerUC.SetCheckerOptions(testCheckerName, &userId, &domainId, &serviceId, opts); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify the configuration was stored at service scope.
|
||||
configs, err := db.GetCheckerConfiguration(testCheckerName, &userId, &domainId, &serviceId)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve config: %v", err)
|
||||
}
|
||||
// Find the service-level entry (UserId, DomainId, ServiceId all set).
|
||||
found := false
|
||||
for _, c := range configs {
|
||||
if c.UserId != nil && c.DomainId != nil && c.ServiceId != nil {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("expected a service-level configuration entry to be stored")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SetOptions_DomainLevel(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
userId, _ := happydns.NewRandomIdentifier()
|
||||
domainId, _ := happydns.NewRandomIdentifier()
|
||||
|
||||
opts := happydns.CheckerOptions{"domain-param": "val"}
|
||||
if err := checkerUC.SetCheckerOptions(testCheckerName, &userId, &domainId, nil, opts); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
configs, err := db.GetCheckerConfiguration(testCheckerName, &userId, &domainId, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve config: %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, c := range configs {
|
||||
if c.UserId != nil && c.DomainId != nil && c.ServiceId == nil {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("expected a domain-level configuration entry to be stored")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SetOptions_UserLevel(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
userId, _ := happydns.NewRandomIdentifier()
|
||||
|
||||
opts := happydns.CheckerOptions{"user-param": "val"}
|
||||
if err := checkerUC.SetCheckerOptions(testCheckerName, &userId, nil, nil, opts); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
configs, err := db.GetCheckerConfiguration(testCheckerName, &userId, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve config: %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, c := range configs {
|
||||
if c.UserId != nil && c.DomainId == nil && c.ServiceId == nil {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("expected a user-level configuration entry to be stored")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SetOptions_AdminLevel(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
opts := happydns.CheckerOptions{"run-param": "admin-val"}
|
||||
if err := checkerUC.SetCheckerOptions(testCheckerName, nil, nil, nil, opts); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
configs, err := db.GetCheckerConfiguration(testCheckerName, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve config: %v", err)
|
||||
}
|
||||
if len(configs) == 0 {
|
||||
t.Error("expected at least one admin-level configuration entry to be stored")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SetOptions_UnknownCheckerErrors(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
opts := happydns.CheckerOptions{"param": "val"}
|
||||
if err := checkerUC.SetCheckerOptions("unknown-checker-xyz", nil, nil, nil, opts); err == nil {
|
||||
t.Fatal("expected error for unknown checker")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OverwriteSomeCheckerOptions tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Test_Overwrite_MergesWithExisting(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
// Pre-seed existing options at admin scope.
|
||||
if err := db.UpdateCheckerConfiguration(testCheckerName, nil, nil, nil, happydns.CheckerOptions{"a": "1"}); err != nil {
|
||||
t.Fatalf("failed to seed option: %v", err)
|
||||
}
|
||||
|
||||
if err := checkerUC.OverwriteSomeCheckerOptions(testCheckerName, nil, nil, nil, happydns.CheckerOptions{"b": "2"}); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Retrieve the stored options and verify both keys are present.
|
||||
configs, err := db.GetCheckerConfiguration(testCheckerName, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve config: %v", err)
|
||||
}
|
||||
if len(configs) == 0 {
|
||||
t.Fatal("expected at least one config entry")
|
||||
}
|
||||
merged := configs[0].Options
|
||||
if merged["a"] != "1" {
|
||||
t.Errorf("expected a='1' to be preserved, got %v", merged["a"])
|
||||
}
|
||||
if merged["b"] != "2" {
|
||||
t.Errorf("expected b='2' to be added, got %v", merged["b"])
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Overwrite_OverridesExistingKey(t *testing.T) {
|
||||
db := newOptionsTestDB(t)
|
||||
checkerUC := newTestCheckerUsecase(db)
|
||||
|
||||
// Pre-seed existing options.
|
||||
if err := db.UpdateCheckerConfiguration(testCheckerName, nil, nil, nil, happydns.CheckerOptions{"a": "1"}); err != nil {
|
||||
t.Fatalf("failed to seed option: %v", err)
|
||||
}
|
||||
|
||||
if err := checkerUC.OverwriteSomeCheckerOptions(testCheckerName, nil, nil, nil, happydns.CheckerOptions{"a": "99"}); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
configs, err := db.GetCheckerConfiguration(testCheckerName, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve config: %v", err)
|
||||
}
|
||||
if len(configs) == 0 {
|
||||
t.Fatal("expected at least one config entry")
|
||||
}
|
||||
if configs[0].Options["a"] != "99" {
|
||||
t.Errorf("expected a='99' after overwrite, got %v", configs[0].Options["a"])
|
||||
}
|
||||
}
|
||||
46
internal/usecase/check/check_storage.go
Normal file
46
internal/usecase/check/check_storage.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2026 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package check
|
||||
|
||||
import (
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
type CheckerStorage interface {
|
||||
// ListAllCheckConfigurations retrieves the list of known Providers.
|
||||
ListAllCheckerConfigurations() (happydns.Iterator[happydns.CheckerOptions], error)
|
||||
|
||||
// ListCheckerConfiguration retrieves all providers own by the given User.
|
||||
ListCheckerConfiguration(string) ([]*happydns.CheckerOptionsPositional, error)
|
||||
|
||||
// GetCheckerConfiguration retrieves the full Provider with the given identifier and owner.
|
||||
GetCheckerConfiguration(string, *happydns.Identifier, *happydns.Identifier, *happydns.Identifier) ([]*happydns.CheckerOptionsPositional, error)
|
||||
|
||||
// UpdateCheckerConfiguration updates the fields of the given Provider.
|
||||
UpdateCheckerConfiguration(string, *happydns.Identifier, *happydns.Identifier, *happydns.Identifier, happydns.CheckerOptions) error
|
||||
|
||||
// DeleteCheckerConfiguration removes the given Provider from the database.
|
||||
DeleteCheckerConfiguration(string, *happydns.Identifier, *happydns.Identifier, *happydns.Identifier) error
|
||||
|
||||
// ClearCheckerConfigurations deletes all Providers present in the database.
|
||||
ClearCheckerConfigurations() error
|
||||
}
|
||||
|
|
@ -22,7 +22,10 @@
|
|||
package check
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"git.happydns.org/happyDomain/checks"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
|
|
@ -30,11 +33,13 @@ import (
|
|||
|
||||
type checkerUsecase struct {
|
||||
config *happydns.Options
|
||||
store CheckerStorage
|
||||
}
|
||||
|
||||
func NewCheckerUsecase(cfg *happydns.Options) happydns.CheckerUsecase {
|
||||
func NewCheckerUsecase(cfg *happydns.Options, store CheckerStorage) happydns.CheckerUsecase {
|
||||
return &checkerUsecase{
|
||||
config: cfg,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +52,84 @@ func (tu *checkerUsecase) GetChecker(cname string) (happydns.Checker, error) {
|
|||
return checker, nil
|
||||
}
|
||||
|
||||
// copyNonEmpty copies key/value pairs from src into dst, skipping nil or empty-string values.
|
||||
func copyNonEmpty(dst, src happydns.CheckerOptions) {
|
||||
for k, v := range src {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if s, ok := v.(string); ok && s == "" {
|
||||
continue
|
||||
}
|
||||
dst[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func compareIdentifiers(a, b *happydns.Identifier) int {
|
||||
if a == nil && b == nil {
|
||||
return 0
|
||||
}
|
||||
if a == nil {
|
||||
return -1
|
||||
}
|
||||
if b == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if a.Equals(*b) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return a.Compare(*b)
|
||||
}
|
||||
|
||||
// CompareCheckerOptionsPositional defines the merge precedence ordering for
|
||||
// checker option configs: admin < user < domain < service.
|
||||
func CompareCheckerOptionsPositional(a, b *happydns.CheckerOptionsPositional) int {
|
||||
if a.CheckName != b.CheckName {
|
||||
return cmp.Compare(a.CheckName, b.CheckName)
|
||||
}
|
||||
if res := compareIdentifiers(a.UserId, b.UserId); res != 0 {
|
||||
return res
|
||||
}
|
||||
if res := compareIdentifiers(a.DomainId, b.DomainId); res != 0 {
|
||||
return res
|
||||
}
|
||||
return compareIdentifiers(a.ServiceId, b.ServiceId)
|
||||
}
|
||||
|
||||
func (tu *checkerUsecase) GetCheckerOptions(cname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier) (*happydns.CheckerOptions, error) {
|
||||
configs, err := tu.store.GetCheckerConfiguration(cname, userid, domainid, serviceid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slices.SortFunc(configs, CompareCheckerOptionsPositional)
|
||||
|
||||
opts := make(happydns.CheckerOptions)
|
||||
|
||||
for _, c := range configs {
|
||||
maps.Copy(opts, c.Options)
|
||||
}
|
||||
|
||||
return &opts, nil
|
||||
}
|
||||
|
||||
func (tu *checkerUsecase) ListCheckers() (*map[string]happydns.Checker, error) {
|
||||
return checks.GetCheckers(), nil
|
||||
}
|
||||
|
||||
func (tu *checkerUsecase) SetCheckerOptions(cname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier, opts happydns.CheckerOptions) error {
|
||||
return tu.store.UpdateCheckerConfiguration(cname, userid, domainid, serviceid, opts)
|
||||
}
|
||||
|
||||
func (tu *checkerUsecase) OverwriteSomeCheckerOptions(cname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier, opts happydns.CheckerOptions) error {
|
||||
current, err := tu.GetCheckerOptions(cname, userid, domainid, serviceid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maps.Copy(*current, opts)
|
||||
|
||||
return tu.store.UpdateCheckerConfiguration(cname, userid, domainid, serviceid, *current)
|
||||
}
|
||||
|
|
|
|||
85
internal/usecase/check/check_usecase_test.go
Normal file
85
internal/usecase/check/check_usecase_test.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package check_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
uc "git.happydns.org/happyDomain/internal/usecase/check"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
func TestSortByCheckName(t *testing.T) {
|
||||
slice := []*happydns.CheckerOptionsPositional{
|
||||
{CheckName: "zeta"},
|
||||
{CheckName: "alpha"},
|
||||
{CheckName: "beta"},
|
||||
}
|
||||
|
||||
slices.SortFunc(slice, uc.CompareCheckerOptionsPositional)
|
||||
|
||||
got := []string{slice[0].CheckName, slice[1].CheckName, slice[2].CheckName}
|
||||
want := []string{"alpha", "beta", "zeta"}
|
||||
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("expected %v, got %v", want, got)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilBeforeNonNil(t *testing.T) {
|
||||
uid, _ := happydns.NewRandomIdentifier()
|
||||
slice := []*happydns.CheckerOptionsPositional{
|
||||
{CheckName: "alpha", UserId: &uid},
|
||||
{CheckName: "alpha", UserId: nil},
|
||||
}
|
||||
|
||||
slices.SortFunc(slice, uc.CompareCheckerOptionsPositional)
|
||||
|
||||
if slice[0].UserId != nil {
|
||||
t.Errorf("expected nil UserId first, got %+v", slice[0].UserId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainIdOrder(t *testing.T) {
|
||||
did, _ := happydns.NewRandomIdentifier()
|
||||
slice := []*happydns.CheckerOptionsPositional{
|
||||
{CheckName: "alpha", UserId: nil, DomainId: &did},
|
||||
{CheckName: "alpha", UserId: nil, DomainId: nil},
|
||||
}
|
||||
|
||||
slices.SortFunc(slice, uc.CompareCheckerOptionsPositional)
|
||||
|
||||
if slice[0].DomainId != nil {
|
||||
t.Errorf("expected nil DomainId first, got %+v", slice[0].DomainId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceIdOrder(t *testing.T) {
|
||||
sid, _ := happydns.NewRandomIdentifier()
|
||||
slice := []*happydns.CheckerOptionsPositional{
|
||||
{CheckName: "alpha", UserId: nil, DomainId: nil, ServiceId: &sid},
|
||||
{CheckName: "alpha", UserId: nil, DomainId: nil, ServiceId: nil},
|
||||
}
|
||||
|
||||
slices.SortFunc(slice, uc.CompareCheckerOptionsPositional)
|
||||
|
||||
if slice[0].ServiceId != nil {
|
||||
t.Errorf("expected nil ServiceId first, got %+v", slice[0].ServiceId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStableGrouping(t *testing.T) {
|
||||
uid, _ := happydns.NewRandomIdentifier()
|
||||
|
||||
slice := []*happydns.CheckerOptionsPositional{
|
||||
{CheckName: "alpha", UserId: &uid},
|
||||
{CheckName: "alpha", UserId: &uid},
|
||||
}
|
||||
|
||||
slices.SortFunc(slice, uc.CompareCheckerOptionsPositional)
|
||||
if slice[0].CheckName != slice[1].CheckName {
|
||||
t.Errorf("expected grouping, got %+v vs %+v", slice[0], slice[1])
|
||||
}
|
||||
}
|
||||
|
|
@ -36,11 +36,14 @@ const (
|
|||
|
||||
type CheckResultStatus int
|
||||
|
||||
type CheckerOptions map[string]any
|
||||
|
||||
type Checker interface {
|
||||
ID() string
|
||||
Name() string
|
||||
Availability() CheckerAvailability
|
||||
RunCheck(ctx context.Context, options map[string]any, meta map[string]string) (*CheckResult, error)
|
||||
Options() CheckerOptionsDocumentation
|
||||
RunCheck(ctx context.Context, options CheckerOptions, meta map[string]string) (*CheckResult, error)
|
||||
}
|
||||
|
||||
// CheckIntervalSpec describes the scheduling bounds for a checker.
|
||||
|
|
@ -58,9 +61,23 @@ type CheckerIntervalProvider interface {
|
|||
}
|
||||
|
||||
type CheckerResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Availability CheckerAvailability `json:"availability"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Availability CheckerAvailability `json:"availability"`
|
||||
Options CheckerOptionsDocumentation `json:"options"`
|
||||
}
|
||||
|
||||
type SetCheckerOptionsRequest struct {
|
||||
Options CheckerOptions `json:"options"`
|
||||
}
|
||||
|
||||
type CheckerOptionsPositional struct {
|
||||
CheckName string
|
||||
UserId *Identifier
|
||||
DomainId *Identifier
|
||||
ServiceId *Identifier
|
||||
|
||||
Options CheckerOptions
|
||||
}
|
||||
|
||||
type CheckerAvailability struct {
|
||||
|
|
@ -70,7 +87,20 @@ type CheckerAvailability struct {
|
|||
LimitToServices []string `json:"limitToServices,omitempty"`
|
||||
}
|
||||
|
||||
type CheckerOptionsDocumentation struct {
|
||||
RunOpts []CheckerOptionDocumentation `json:"runOpts,omitempty"`
|
||||
ServiceOpts []CheckerOptionDocumentation `json:"serviceOpts,omitempty"`
|
||||
DomainOpts []CheckerOptionDocumentation `json:"domainOpts,omitempty"`
|
||||
UserOpts []CheckerOptionDocumentation `json:"userOpts,omitempty"`
|
||||
AdminOpts []CheckerOptionDocumentation `json:"adminOpts,omitempty"`
|
||||
}
|
||||
|
||||
type CheckerOptionDocumentation Field
|
||||
|
||||
type CheckerUsecase interface {
|
||||
GetChecker(string) (Checker, error)
|
||||
GetCheckerOptions(string, *Identifier, *Identifier, *Identifier) (*CheckerOptions, error)
|
||||
ListCheckers() (*map[string]Checker, error)
|
||||
OverwriteSomeCheckerOptions(string, *Identifier, *Identifier, *Identifier, CheckerOptions) error
|
||||
SetCheckerOptions(string, *Identifier, *Identifier, *Identifier, CheckerOptions) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"slices"
|
||||
)
|
||||
|
||||
const IDENTIFIER_LEN = 16
|
||||
|
|
@ -55,6 +56,10 @@ func (i Identifier) Equals(other Identifier) bool {
|
|||
return bytes.Equal(i, other)
|
||||
}
|
||||
|
||||
func (i Identifier) Compare(other Identifier) int {
|
||||
return slices.Compare(i, other)
|
||||
}
|
||||
|
||||
func (i *Identifier) String() string {
|
||||
return base64.RawURLEncoding.EncodeToString(*i)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue