فهرست منبع

chore: golangci-lint fixes

jamesread 1 ماه پیش
والد
کامیت
7ecc7791d8

+ 7 - 0
service/.golangci.yml

@@ -9,6 +9,7 @@ linters:
   enable:
   enable:
     - bidichk
     - bidichk
     - bodyclose
     - bodyclose
+    - copyloopvar
     - durationcheck
     - durationcheck
     - errcheck
     - errcheck
     - errorlint
     - errorlint
@@ -18,11 +19,17 @@ linters:
     - govet
     - govet
     - ineffassign
     - ineffassign
     - misspell
     - misspell
+#    - modernize
     - nilerr
     - nilerr
     - noctx
     - noctx
+#    - promlinter
     - staticcheck
     - staticcheck
+#    - testifylint
+    - thelper
     - unconvert
     - unconvert
+#    - unparam
     - unused
     - unused
+    - usestdlibvars
   settings:
   settings:
     gocyclo:
     gocyclo:
       min-complexity: 5
       min-complexity: 5

+ 11 - 10
service/cmd/config-tool/main.go

@@ -3,6 +3,7 @@ package main
 import (
 import (
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
+	"maps"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"strconv"
 	"strconv"
@@ -98,18 +99,18 @@ func userDisplayName(username string, index int) string {
 	return username
 	return username
 }
 }
 
 
-func copyUserMapWithPassword(userMap map[string]interface{}, hashedPassword string) map[string]interface{} {
-	newUserMap := make(map[string]interface{}, len(userMap)+1)
-	for key, value := range userMap {
-		newUserMap[key] = value
-	}
+func copyUserMapWithPassword(userMap map[string]any, hashedPassword string) map[string]any {
+	newUserMap := make(map[string]any, len(userMap)+1)
+
+	maps.Copy(newUserMap, userMap)
+
 	newUserMap["password"] = hashedPassword
 	newUserMap["password"] = hashedPassword
 
 
 	return newUserMap
 	return newUserMap
 }
 }
 
 
-func resetPasswordInUserMap(userValue interface{}, index int, hashedPassword string) interface{} {
-	userMap, ok := userValue.(map[string]interface{})
+func resetPasswordInUserMap(userValue any, index int, hashedPassword string) any {
+	userMap, ok := userValue.(map[string]any)
 	if !ok {
 	if !ok {
 		log.Warnf("User entry at index %d is not a map, skipping", index)
 		log.Warnf("User entry at index %d is not a map, skipping", index)
 		return userValue
 		return userValue
@@ -122,8 +123,8 @@ func resetPasswordInUserMap(userValue interface{}, index int, hashedPassword str
 	return copyUserMapWithPassword(userMap, hashedPassword)
 	return copyUserMapWithPassword(userMap, hashedPassword)
 }
 }
 
 
-func resetPasswordsFromSlice(k *koanf.Koanf, usersSliceTyped []interface{}, hashedPassword string) {
-	newUsersSlice := make([]interface{}, len(usersSliceTyped))
+func resetPasswordsFromSlice(k *koanf.Koanf, usersSliceTyped []any, hashedPassword string) {
+	newUsersSlice := make([]any, len(usersSliceTyped))
 	for index, userValue := range usersSliceTyped {
 	for index, userValue := range usersSliceTyped {
 		newUsersSlice[index] = resetPasswordInUserMap(userValue, index, hashedPassword)
 		newUsersSlice[index] = resetPasswordInUserMap(userValue, index, hashedPassword)
 	}
 	}
@@ -155,7 +156,7 @@ func hasLocalUsers(cfg *config.Config) bool {
 }
 }
 
 
 func applyPasswordResets(k *koanf.Koanf, cfg *config.Config, hashedPassword string) {
 func applyPasswordResets(k *koanf.Koanf, cfg *config.Config, hashedPassword string) {
-	usersSliceTyped, ok := k.Get("authLocalUsers.users").([]interface{})
+	usersSliceTyped, ok := k.Get("authLocalUsers.users").([]any)
 	if ok && len(usersSliceTyped) > 0 {
 	if ok && len(usersSliceTyped) > 0 {
 		resetPasswordsFromSlice(k, usersSliceTyped, hashedPassword)
 		resetPasswordsFromSlice(k, usersSliceTyped, hashedPassword)
 		return
 		return

+ 4 - 4
service/internal/api/api.go

@@ -807,13 +807,13 @@ func paginate(total int64, size int64, start int64) pageInfo {
 	if start < 0 {
 	if start < 0 {
 		start = 0
 		start = 0
 	}
 	}
+
 	if start >= total {
 	if start >= total {
 		return pageInfo{total: total, size: size, start: start, end: start, empty: true}
 		return pageInfo{total: total, size: size, start: start, end: start, empty: true}
 	}
 	}
-	end := start + size
-	if end > total {
-		end = total
-	}
+
+	end := min(start+size, total)
+
 	return pageInfo{total: total, size: size, start: start, end: end, empty: false}
 	return pageInfo{total: total, size: size, start: start, end: end, empty: false}
 }
 }
 
 

+ 3 - 3
service/internal/api/api_log_arguments.go

@@ -2,6 +2,7 @@ package api
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"maps"
 	"sort"
 	"sort"
 	"strings"
 	"strings"
 
 
@@ -35,9 +36,8 @@ func logEntryArgumentsToProto(args map[string]string) []*apiv1.StartActionArgume
 
 
 func copyStringMap(source map[string]string) map[string]string {
 func copyStringMap(source map[string]string) map[string]string {
 	copied := make(map[string]string, len(source))
 	copied := make(map[string]string, len(source))
-	for key, value := range source {
-		copied[key] = value
-	}
+
+	maps.Copy(copied, source)
 
 
 	return copied
 	return copied
 }
 }

+ 2 - 2
service/internal/api/api_log_arguments_test.go

@@ -75,7 +75,7 @@ func waitForLogJustification(t *testing.T, ex *executor.Executor, trackingID, ex
 func TestExecutionStatusIncludesStoredArguments(t *testing.T) {
 func TestExecutionStatusIncludesStoredArguments(t *testing.T) {
 	cfg := config.DefaultConfig()
 	cfg := config.DefaultConfig()
 	cfg.Actions = []*config.Action{
 	cfg.Actions = []*config.Action{
-		argumentAction("Ping host", "echo {{ host }}", []config.ActionArgument{
+		argumentAction("Ping host with stored args", "echo {{ host }}", []config.ActionArgument{
 			{Name: "host", Type: "ascii_identifier"},
 			{Name: "host", Type: "ascii_identifier"},
 		}),
 		}),
 	}
 	}
@@ -236,7 +236,7 @@ func TestRestartActionRejectsIncompleteStoredArguments(t *testing.T) {
 func TestRestartActionRejectsMissingRequiredStoredArguments(t *testing.T) {
 func TestRestartActionRejectsMissingRequiredStoredArguments(t *testing.T) {
 	cfg := config.DefaultConfig()
 	cfg := config.DefaultConfig()
 	cfg.Actions = []*config.Action{
 	cfg.Actions = []*config.Action{
-		argumentAction("Ping host", "echo {{ host }}", []config.ActionArgument{
+		argumentAction("Ping host - reject missing required stored arg", "echo {{ host }}", []config.ActionArgument{
 			{Name: "host", Type: "ascii_identifier"},
 			{Name: "host", Type: "ascii_identifier"},
 		}),
 		}),
 	}
 	}

+ 24 - 14
service/internal/api/api_test.go

@@ -29,6 +29,8 @@ func getNewTestServerAndClient(injectedConfig *config.Config) (*httptest.Server,
 }
 }
 
 
 func getNewTestServerAndClientWithExecutor(injectedConfig *config.Config, ex *executor.Executor) (*httptest.Server, apiv1connect.OliveTinApiServiceClient) {
 func getNewTestServerAndClientWithExecutor(injectedConfig *config.Config, ex *executor.Executor) (*httptest.Server, apiv1connect.OliveTinApiServiceClient) {
+	ex.Cfg = injectedConfig
+
 	apiPath, apiHandler := GetNewHandler(ex)
 	apiPath, apiHandler := GetNewHandler(ex)
 
 
 	mux := http.NewServeMux()
 	mux := http.NewServeMux()
@@ -102,8 +104,6 @@ func TestGetActionsAndStart(t *testing.T) {
 
 
 	log.Infof("GetReadyz response: %v", respGetReady.Msg)
 	log.Infof("GetReadyz response: %v", respGetReady.Msg)
 
 
-	assert.Equal(t, true, true, "sayHello Failed")
-
 	//	assert.Equal(t, 1, len(respGb.Msg.Actions), "Got 1 action button back")
 	//	assert.Equal(t, 1, len(respGb.Msg.Actions), "Got 1 action button back")
 
 
 	log.Printf("Response: %+v", respInit)
 	log.Printf("Response: %+v", respInit)
@@ -112,7 +112,7 @@ func TestGetActionsAndStart(t *testing.T) {
 		//		ActionId: "blat"
 		//		ActionId: "blat"
 	}))
 	}))
 
 
-	assert.NotNil(t, err, "Error 404 after start action")
+	require.Error(t, err, "Error 404 after start action")
 	assert.Nil(t, respSa, "Nil response for non existing action")
 	assert.Nil(t, respSa, "Nil response for non existing action")
 
 
 	defer conn.Close()
 	defer conn.Close()
@@ -137,12 +137,12 @@ func TestGetEntities(t *testing.T) {
 
 
 	resp, err := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{}))
 	resp, err := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{}))
 
 
-	assert.NoError(t, err, "GetEntities should not return an error")
+	require.NoError(t, err, "GetEntities should not return an error")
 	assert.NotNil(t, resp, "GetEntities response should not be nil")
 	assert.NotNil(t, resp, "GetEntities response should not be nil")
 	assert.NotNil(t, resp.Msg, "GetEntities response message should not be nil")
 	assert.NotNil(t, resp.Msg, "GetEntities response message should not be nil")
 
 
 	entityDefinitions := resp.Msg.EntityDefinitions
 	entityDefinitions := resp.Msg.EntityDefinitions
-	assert.Equal(t, 3, len(entityDefinitions), "Should return 3 entity definitions")
+	assert.Len(t, entityDefinitions, 3, "Should return 3 entity definitions")
 
 
 	validateEntityOrderAndStructure(t, entityDefinitions)
 	validateEntityOrderAndStructure(t, entityDefinitions)
 	validateNoDuplicates(t, entityDefinitions)
 	validateNoDuplicates(t, entityDefinitions)
@@ -151,6 +151,8 @@ func TestGetEntities(t *testing.T) {
 }
 }
 
 
 func validateEntityListProperties(t *testing.T, client apiv1connect.OliveTinApiServiceClient) {
 func validateEntityListProperties(t *testing.T, client apiv1connect.OliveTinApiServiceClient) {
+	t.Helper()
+
 	resp, err := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{
 	resp, err := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{
 		EntityType: "server",
 		EntityType: "server",
 		Page:       1,
 		Page:       1,
@@ -185,21 +187,25 @@ func setupTestEntities() {
 }
 }
 
 
 func validateEntityOrderAndStructure(t *testing.T, entityDefinitions []*apiv1.EntityDefinition) {
 func validateEntityOrderAndStructure(t *testing.T, entityDefinitions []*apiv1.EntityDefinition) {
+	t.Helper()
+
 	assert.Equal(t, "application", entityDefinitions[0].Title, "First entity should be 'application' (alphabetically first)")
 	assert.Equal(t, "application", entityDefinitions[0].Title, "First entity should be 'application' (alphabetically first)")
-	assert.Equal(t, 1, len(entityDefinitions[0].Instances), "Application should have 1 instance")
+	assert.Len(t, entityDefinitions[0].Instances, 1, "Application should have 1 instance")
 	assert.Equal(t, "webapp", entityDefinitions[0].Instances[0].UniqueKey, "Application instance should be 'webapp'")
 	assert.Equal(t, "webapp", entityDefinitions[0].Instances[0].UniqueKey, "Application instance should be 'webapp'")
 
 
 	assert.Equal(t, "database", entityDefinitions[1].Title, "Second entity should be 'database' (alphabetically second)")
 	assert.Equal(t, "database", entityDefinitions[1].Title, "Second entity should be 'database' (alphabetically second)")
-	assert.Equal(t, 2, len(entityDefinitions[1].Instances), "Database should have 2 instances")
+	assert.Len(t, entityDefinitions[1].Instances, 2, "Database should have 2 instances")
 	assert.Equal(t, "mysql", entityDefinitions[1].Instances[0].UniqueKey, "First database instance should be 'mysql' (alphabetically first)")
 	assert.Equal(t, "mysql", entityDefinitions[1].Instances[0].UniqueKey, "First database instance should be 'mysql' (alphabetically first)")
 	assert.Equal(t, "postgres", entityDefinitions[1].Instances[1].UniqueKey, "Second database instance should be 'postgres' (alphabetically second)")
 	assert.Equal(t, "postgres", entityDefinitions[1].Instances[1].UniqueKey, "Second database instance should be 'postgres' (alphabetically second)")
 
 
 	assert.Equal(t, "server", entityDefinitions[2].Title, "Third entity should be 'server' (alphabetically third)")
 	assert.Equal(t, "server", entityDefinitions[2].Title, "Third entity should be 'server' (alphabetically third)")
-	assert.Equal(t, 0, len(entityDefinitions[2].Instances), "Server instances should not be included in bulk list response")
+	assert.Empty(t, entityDefinitions[2].Instances, "Server instances should not be included in bulk list response")
 	assert.Equal(t, int32(3), entityDefinitions[2].TotalInstances, "Server should report total instance count")
 	assert.Equal(t, int32(3), entityDefinitions[2].TotalInstances, "Server should report total instance count")
 }
 }
 
 
 func validateNoDuplicates(t *testing.T, entityDefinitions []*apiv1.EntityDefinition) {
 func validateNoDuplicates(t *testing.T, entityDefinitions []*apiv1.EntityDefinition) {
+	t.Helper()
+
 	instanceKeys := make(map[string]map[string]bool)
 	instanceKeys := make(map[string]map[string]bool)
 	for _, def := range entityDefinitions {
 	for _, def := range entityDefinitions {
 		instanceKeys[def.Title] = make(map[string]bool)
 		instanceKeys[def.Title] = make(map[string]bool)
@@ -211,13 +217,15 @@ func validateNoDuplicates(t *testing.T, entityDefinitions []*apiv1.EntityDefinit
 }
 }
 
 
 func validateConsistency(t *testing.T, client apiv1connect.OliveTinApiServiceClient, entityDefinitions []*apiv1.EntityDefinition) {
 func validateConsistency(t *testing.T, client apiv1connect.OliveTinApiServiceClient, entityDefinitions []*apiv1.EntityDefinition) {
+	t.Helper()
+
 	resp2, err2 := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{}))
 	resp2, err2 := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{}))
-	assert.NoError(t, err2, "Second GetEntities call should not return an error")
-	assert.Equal(t, len(entityDefinitions), len(resp2.Msg.EntityDefinitions), "Second call should return same number of entity definitions")
+	require.NoError(t, err2, "Second GetEntities call should not return an error")
+	assert.Len(t, entityDefinitions, len(resp2.Msg.EntityDefinitions), "Second call should return same number of entity definitions")
 
 
 	for i, def := range entityDefinitions {
 	for i, def := range entityDefinitions {
 		assert.Equal(t, def.Title, resp2.Msg.EntityDefinitions[i].Title, "Entity order should be consistent across calls")
 		assert.Equal(t, def.Title, resp2.Msg.EntityDefinitions[i].Title, "Entity order should be consistent across calls")
-		assert.Equal(t, len(def.Instances), len(resp2.Msg.EntityDefinitions[i].Instances), "Instance count should be consistent")
+		assert.Len(t, def.Instances, len(resp2.Msg.EntityDefinitions[i].Instances), "Instance count should be consistent")
 		for j, inst := range def.Instances {
 		for j, inst := range def.Instances {
 			assert.Equal(t, inst.UniqueKey, resp2.Msg.EntityDefinitions[i].Instances[j].UniqueKey, "Instance order should be consistent across calls")
 			assert.Equal(t, inst.UniqueKey, resp2.Msg.EntityDefinitions[i].Instances[j].UniqueKey, "Instance order should be consistent across calls")
 		}
 		}
@@ -376,6 +384,8 @@ func findBindingByTitle(ex *executor.Executor, title string) *executor.ActionBin
 }
 }
 
 
 func testWithEntity(t *testing.T, binding *executor.ActionBinding, rr *DashboardRenderRequest, enabled bool, expectedCanExec bool, message string) {
 func testWithEntity(t *testing.T, binding *executor.ActionBinding, rr *DashboardRenderRequest, enabled bool, expectedCanExec bool, message string) {
+	t.Helper()
+
 	binding.Entity = &entities.Entity{
 	binding.Entity = &entities.Entity{
 		UniqueKey: "test-entity",
 		UniqueKey: "test-entity",
 		Data:      map[string]any{"enabled": enabled},
 		Data:      map[string]any{"enabled": enabled},
@@ -809,12 +819,12 @@ func TestRegisterStreamingClientEnforcesLimit(t *testing.T) {
 	}
 	}
 	close(overflow.heartbeatDone)
 	close(overflow.heartbeatDone)
 	err := api.registerStreamingClient(overflow)
 	err := api.registerStreamingClient(overflow)
-	assert.ErrorIs(t, err, errEventStreamClientLimit)
-	assert.Equal(t, maxEventStreamClients, len(api.streamingClients))
+	require.ErrorIs(t, err, errEventStreamClientLimit)
+	assert.Len(t, api.streamingClients, maxEventStreamClients)
 
 
 	api.removeClient(clients[0])
 	api.removeClient(clients[0])
 	require.NoError(t, api.registerStreamingClient(overflow))
 	require.NoError(t, api.registerStreamingClient(overflow))
-	assert.Equal(t, maxEventStreamClients, len(api.streamingClients))
+	assert.Len(t, api.streamingClients, maxEventStreamClients)
 
 
 	for _, client := range clients[1:] {
 	for _, client := range clients[1:] {
 		api.removeClient(client)
 		api.removeClient(client)

+ 3 - 3
service/internal/auth/otjwt/jwt.go

@@ -156,7 +156,7 @@ func parseJwtTokenWithLocalKey(cfg *config.Config, jwtString string) (*jwt.Token
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	keyFunc := func(token *jwt.Token) (interface{}, error) {
+	keyFunc := func(token *jwt.Token) (any, error) {
 		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
 		if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
 			return nil, fmt.Errorf("parseJwt expected token algorithm RSA but got: %v", token.Header["alg"])
 			return nil, fmt.Errorf("parseJwt expected token algorithm RSA but got: %v", token.Header["alg"])
 		}
 		}
@@ -170,7 +170,7 @@ func parseJwtTokenWithLocalKey(cfg *config.Config, jwtString string) (*jwt.Token
 
 
 // Hash-based Message Authentication Code
 // Hash-based Message Authentication Code
 func parseJwtTokenWithHMAC(cfg *config.Config, jwtString string) (*jwt.Token, error) {
 func parseJwtTokenWithHMAC(cfg *config.Config, jwtString string) (*jwt.Token, error) {
-	keyFunc := func(token *jwt.Token) (interface{}, error) {
+	keyFunc := func(token *jwt.Token) (any, error) {
 		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
 		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
 			return nil, fmt.Errorf("parseJwt expected token algorithm HMAC but got: %v", token.Header["alg"])
 			return nil, fmt.Errorf("parseJwt expected token algorithm HMAC but got: %v", token.Header["alg"])
 		}
 		}
@@ -237,7 +237,7 @@ func parseJwt(cfg *config.Config, token string) *authTypes.AuthenticatedUser {
 func parseGroupClaim(groupClaim string, claims jwt.MapClaims) string {
 func parseGroupClaim(groupClaim string, claims jwt.MapClaims) string {
 	usergroup := ""
 	usergroup := ""
 	if val, ok := claims[groupClaim]; ok {
 	if val, ok := claims[groupClaim]; ok {
-		if array, ok := val.([]interface{}); ok {
+		if array, ok := val.([]any); ok {
 			groups := make([]string, len(array))
 			groups := make([]string, len(array))
 			for i, v := range array {
 			for i, v := range array {
 				groups[i] = fmt.Sprintf("%s", v)
 				groups[i] = fmt.Sprintf("%s", v)

+ 27 - 9
service/internal/auth/otjwt/jwt_test.go

@@ -20,6 +20,8 @@ import (
 )
 )
 
 
 func generateRSAKeyPair(t *testing.T) (*rsa.PrivateKey, []byte) {
 func generateRSAKeyPair(t *testing.T) (*rsa.PrivateKey, []byte) {
+	t.Helper()
+
 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
 	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("failed to generate RSA key: %v", err)
 		t.Fatalf("failed to generate RSA key: %v", err)
@@ -42,6 +44,8 @@ func generateRSAKeyPair(t *testing.T) (*rsa.PrivateKey, []byte) {
 }
 }
 
 
 func createKeys(t *testing.T) (*rsa.PrivateKey, string) {
 func createKeys(t *testing.T) (*rsa.PrivateKey, string) {
+	t.Helper()
+
 	tmpFile, err := os.CreateTemp(os.TempDir(), "olivetin-jwt-")
 	tmpFile, err := os.CreateTemp(os.TempDir(), "olivetin-jwt-")
 	if err != nil {
 	if err != nil {
 		t.Fatalf("failed to create temp file: %v", err)
 		t.Fatalf("failed to create temp file: %v", err)
@@ -66,6 +70,8 @@ func newMux() *http.ServeMux {
 }
 }
 
 
 func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.PrivateKey, expire int64, audience string) string {
 func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.PrivateKey, expire int64, audience string) string {
+	t.Helper()
+
 	token := jwt.New(jwt.SigningMethodRS256)
 	token := jwt.New(jwt.SigningMethodRS256)
 	claims := token.Claims.(jwt.MapClaims)
 	claims := token.Claims.(jwt.MapClaims)
 	claims["nbf"] = time.Now().Unix() - 1000
 	claims["nbf"] = time.Now().Unix() - 1000
@@ -84,6 +90,8 @@ func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.Priva
 }
 }
 
 
 func setupJWTTestHandler(t *testing.T, cfg *config.Config) http.Handler {
 func setupJWTTestHandler(t *testing.T, cfg *config.Config) http.Handler {
+	t.Helper()
+
 	mux := newMux()
 	mux := newMux()
 	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		context := &authpublic.AuthCheckingContext{
 		context := &authpublic.AuthCheckingContext{
@@ -93,7 +101,7 @@ func setupJWTTestHandler(t *testing.T, cfg *config.Config) http.Handler {
 		user := CheckUserFromJwtHeader(context)
 		user := CheckUserFromJwtHeader(context)
 
 
 		if user == nil {
 		if user == nil {
-			w.WriteHeader(403)
+			w.WriteHeader(http.StatusForbidden)
 			return
 			return
 		}
 		}
 
 
@@ -104,6 +112,8 @@ func setupJWTTestHandler(t *testing.T, cfg *config.Config) http.Handler {
 }
 }
 
 
 func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) {
 func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) {
+	t.Helper()
+
 	defer func() { _ = res.Body.Close() }()
 	defer func() { _ = res.Body.Close() }()
 	assert.Equal(t, expectCode, res.StatusCode)
 	assert.Equal(t, expectCode, res.StatusCode)
 	body, _ := io.ReadAll(res.Body)
 	body, _ := io.ReadAll(res.Body)
@@ -111,10 +121,14 @@ func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) {
 }
 }
 
 
 func testJwkValidation(t *testing.T, expire int64, expectCode int) {
 func testJwkValidation(t *testing.T, expire int64, expectCode int) {
+	t.Helper()
+
 	testJwkValidationWithAudience(t, expire, expectCode, "", "")
 	testJwkValidationWithAudience(t, expire, expectCode, "", "")
 }
 }
 
 
 func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, configAudience, tokenAudience string) {
 func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, configAudience, tokenAudience string) {
+	t.Helper()
+
 	privateKey, publicKeyPath := createKeys(t)
 	privateKey, publicKeyPath := createKeys(t)
 	defer func() { _ = os.Remove(publicKeyPath) }()
 	defer func() { _ = os.Remove(publicKeyPath) }()
 
 
@@ -143,22 +157,24 @@ func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, c
 }
 }
 
 
 func TestJWTSignatureVerificationSucceeds(t *testing.T) {
 func TestJWTSignatureVerificationSucceeds(t *testing.T) {
-	testJwkValidation(t, 1000, 200)
+	testJwkValidation(t, 1000, http.StatusOK)
 }
 }
 
 
 func TestJWTSignatureVerificationFails(t *testing.T) {
 func TestJWTSignatureVerificationFails(t *testing.T) {
-	testJwkValidation(t, -500, 403)
+	testJwkValidation(t, -500, http.StatusForbidden)
 }
 }
 
 
 func TestJWTAudienceValidationRejectsWrongAudience(t *testing.T) {
 func TestJWTAudienceValidationRejectsWrongAudience(t *testing.T) {
-	testJwkValidationWithAudience(t, 1000, 403, "expected-audience", "wrong-audience")
+	testJwkValidationWithAudience(t, 1000, http.StatusForbidden, "expected-audience", "wrong-audience")
 }
 }
 
 
 func TestJWTAudienceValidationAcceptsCorrectAudience(t *testing.T) {
 func TestJWTAudienceValidationAcceptsCorrectAudience(t *testing.T) {
-	testJwkValidationWithAudience(t, 1000, 200, "expected-audience", "expected-audience")
+	testJwkValidationWithAudience(t, 1000, http.StatusOK, "expected-audience", "expected-audience")
 }
 }
 
 
-func createJWTTokenWithGroups(t *testing.T, privateKey *rsa.PrivateKey, groups interface{}) string {
+func createJWTTokenWithGroups(t *testing.T, privateKey *rsa.PrivateKey, groups any) string {
+	t.Helper()
+
 	token := jwt.New(jwt.SigningMethodRS256)
 	token := jwt.New(jwt.SigningMethodRS256)
 	claims := token.Claims.(jwt.MapClaims)
 	claims := token.Claims.(jwt.MapClaims)
 	claims["nbf"] = time.Now().Unix() - 1000
 	claims["nbf"] = time.Now().Unix() - 1000
@@ -174,7 +190,9 @@ func createJWTTokenWithGroups(t *testing.T, privateKey *rsa.PrivateKey, groups i
 }
 }
 
 
 func makeJWTRequest(t *testing.T, srv *httptest.Server, tokenStr string) *http.Response {
 func makeJWTRequest(t *testing.T, srv *httptest.Server, tokenStr string) *http.Response {
-	req, err := http.NewRequestWithContext(t.Context(), "GET", srv.URL, nil)
+	t.Helper()
+
+	req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, srv.URL, nil)
 	if err != nil {
 	if err != nil {
 		t.Fatalf("failed to create request: %v", err)
 		t.Fatalf("failed to create request: %v", err)
 	}
 	}
@@ -209,7 +227,7 @@ func TestJWTHeader(t *testing.T) {
 		user := CheckUserFromJwtHeader(context)
 		user := CheckUserFromJwtHeader(context)
 
 
 		if user == nil {
 		if user == nil {
-			w.WriteHeader(403)
+			w.WriteHeader(http.StatusForbidden)
 			return
 			return
 		}
 		}
 
 
@@ -223,7 +241,7 @@ func TestJWTHeader(t *testing.T) {
 	res := makeJWTRequest(t, srv, tokenStr)
 	res := makeJWTRequest(t, srv, tokenStr)
 	defer func() { _ = res.Body.Close() }()
 	defer func() { _ = res.Body.Close() }()
 
 
-	assert.Equal(t, 200, res.StatusCode)
+	assert.Equal(t, http.StatusOK, res.StatusCode)
 	body, _ := io.ReadAll(res.Body)
 	body, _ := io.ReadAll(res.Body)
 	t.Logf("Response body: %s", string(body))
 	t.Logf("Response body: %s", string(body))
 }
 }

+ 1 - 1
service/internal/auth/otoauth2/restapi_auth_oauth2.go

@@ -346,7 +346,7 @@ func getUserInfo(cfg *config.Config, client *http.Client, provider *config.OAuth
 
 
 	defer cancel()
 	defer cancel()
 
 
-	req, err := http.NewRequestWithContext(ctx, "GET", provider.WhoamiUrl, nil)
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, provider.WhoamiUrl, nil)
 
 
 	if err != nil {
 	if err != nil {
 		log.Error("Could not construct user data request", err)
 		log.Error("Could not construct user data request", err)

+ 15 - 15
service/internal/config/config_reloader.go

@@ -249,17 +249,17 @@ func loadAndMergeIncludedFile(k *koanf.Koanf, includePath, filename string) {
 	}).Info("Successfully loaded included config file")
 	}).Info("Successfully loaded included config file")
 }
 }
 
 
-func mergeFuncForSource(sourceFile string) func(src, dest map[string]interface{}) error {
-	return func(src map[string]interface{}, dest map[string]interface{}) error {
+func mergeFuncForSource(sourceFile string) func(src, dest map[string]any) error {
+	return func(src map[string]any, dest map[string]any) error {
 		return mergeFunc(src, dest, sourceFile)
 		return mergeFunc(src, dest, sourceFile)
 	}
 	}
 }
 }
 
 
 // mergeActionsWhenBothExist merges actions when both src and dest have actions.
 // mergeActionsWhenBothExist merges actions when both src and dest have actions.
-func mergeActionsWhenBothExist(srcActions interface{}, destActions interface{}, dest map[string]interface{}, sourceFile string) {
+func mergeActionsWhenBothExist(srcActions any, destActions any, dest map[string]any, sourceFile string) {
 	stampSourceOnMaps(srcActions, sourceFile)
 	stampSourceOnMaps(srcActions, sourceFile)
-	srcSlice, ok1 := srcActions.([]interface{})
-	destSlice, ok2 := destActions.([]interface{})
+	srcSlice, ok1 := srcActions.([]any)
+	destSlice, ok2 := destActions.([]any)
 	if ok1 && ok2 {
 	if ok1 && ok2 {
 		dest["actions"] = append(destSlice, srcSlice...)
 		dest["actions"] = append(destSlice, srcSlice...)
 	} else {
 	} else {
@@ -268,7 +268,7 @@ func mergeActionsWhenBothExist(srcActions interface{}, destActions interface{},
 }
 }
 
 
 // mergeActionsFromSource merges actions from source into destination.
 // mergeActionsFromSource merges actions from source into destination.
-func mergeActionsFromSource(srcActions interface{}, dest map[string]interface{}, sourceFile string) {
+func mergeActionsFromSource(srcActions any, dest map[string]any, sourceFile string) {
 	if destActions, ok := dest["actions"]; ok {
 	if destActions, ok := dest["actions"]; ok {
 		mergeActionsWhenBothExist(srcActions, destActions, dest, sourceFile)
 		mergeActionsWhenBothExist(srcActions, destActions, dest, sourceFile)
 	} else {
 	} else {
@@ -278,9 +278,9 @@ func mergeActionsFromSource(srcActions interface{}, dest map[string]interface{},
 }
 }
 
 
 // mergeDashboardsWhenBothExist merges dashboards when both src and dest have dashboards.
 // mergeDashboardsWhenBothExist merges dashboards when both src and dest have dashboards.
-func mergeDashboardsWhenBothExist(srcDashboards interface{}, destDashboards interface{}, dest map[string]interface{}) {
-	srcSlice, ok1 := srcDashboards.([]interface{})
-	destSlice, ok2 := destDashboards.([]interface{})
+func mergeDashboardsWhenBothExist(srcDashboards any, destDashboards any, dest map[string]any) {
+	srcSlice, ok1 := srcDashboards.([]any)
+	destSlice, ok2 := destDashboards.([]any)
 	if ok1 && ok2 {
 	if ok1 && ok2 {
 		dest["dashboards"] = append(destSlice, srcSlice...)
 		dest["dashboards"] = append(destSlice, srcSlice...)
 	} else {
 	} else {
@@ -289,7 +289,7 @@ func mergeDashboardsWhenBothExist(srcDashboards interface{}, destDashboards inte
 }
 }
 
 
 // mergeDashboardsFromSource merges dashboards from source into destination.
 // mergeDashboardsFromSource merges dashboards from source into destination.
-func mergeDashboardsFromSource(srcDashboards interface{}, dest map[string]interface{}) {
+func mergeDashboardsFromSource(srcDashboards any, dest map[string]any) {
 	if destDashboards, ok := dest["dashboards"]; ok {
 	if destDashboards, ok := dest["dashboards"]; ok {
 		mergeDashboardsWhenBothExist(srcDashboards, destDashboards, dest)
 		mergeDashboardsWhenBothExist(srcDashboards, destDashboards, dest)
 	} else {
 	} else {
@@ -298,10 +298,10 @@ func mergeDashboardsFromSource(srcDashboards interface{}, dest map[string]interf
 }
 }
 
 
 // mergeEntitiesWhenBothExist merges entities when both src and dest have entities.
 // mergeEntitiesWhenBothExist merges entities when both src and dest have entities.
-func mergeEntitiesWhenBothExist(srcEntities interface{}, destEntities interface{}, dest map[string]interface{}, sourceFile string) {
+func mergeEntitiesWhenBothExist(srcEntities any, destEntities any, dest map[string]any, sourceFile string) {
 	stampSourceOnMaps(srcEntities, sourceFile)
 	stampSourceOnMaps(srcEntities, sourceFile)
-	srcSlice, ok1 := srcEntities.([]interface{})
-	destSlice, ok2 := destEntities.([]interface{})
+	srcSlice, ok1 := srcEntities.([]any)
+	destSlice, ok2 := destEntities.([]any)
 	if ok1 && ok2 {
 	if ok1 && ok2 {
 		dest["entities"] = append(destSlice, srcSlice...)
 		dest["entities"] = append(destSlice, srcSlice...)
 	} else {
 	} else {
@@ -310,7 +310,7 @@ func mergeEntitiesWhenBothExist(srcEntities interface{}, destEntities interface{
 }
 }
 
 
 // mergeEntitiesFromSource merges entities from source into destination.
 // mergeEntitiesFromSource merges entities from source into destination.
-func mergeEntitiesFromSource(srcEntities interface{}, dest map[string]interface{}, sourceFile string) {
+func mergeEntitiesFromSource(srcEntities any, dest map[string]any, sourceFile string) {
 	if destEntities, ok := dest["entities"]; ok {
 	if destEntities, ok := dest["entities"]; ok {
 		mergeEntitiesWhenBothExist(srcEntities, destEntities, dest, sourceFile)
 		mergeEntitiesWhenBothExist(srcEntities, destEntities, dest, sourceFile)
 	} else {
 	} else {
@@ -319,7 +319,7 @@ func mergeEntitiesFromSource(srcEntities interface{}, dest map[string]interface{
 	}
 	}
 }
 }
 
 
-func mergeFunc(src map[string]interface{}, dest map[string]interface{}, sourceFile string) error {
+func mergeFunc(src map[string]any, dest map[string]any, sourceFile string) error {
 	if srcActions, ok := src["actions"]; ok {
 	if srcActions, ok := src["actions"]; ok {
 		mergeActionsFromSource(srcActions, dest, sourceFile)
 		mergeActionsFromSource(srcActions, dest, sourceFile)
 	}
 	}

+ 3 - 8
service/internal/config/sanitize.go

@@ -2,6 +2,7 @@ package config
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"slices"
 	"strings"
 	"strings"
 	"text/template"
 	"text/template"
 
 
@@ -179,13 +180,7 @@ func (cfg *Config) inlineActionExists(action *Action) bool {
 }
 }
 
 
 func (cfg *Config) inlineActionPointerExists(action *Action) bool {
 func (cfg *Config) inlineActionPointerExists(action *Action) bool {
-	for _, existingAction := range cfg.Actions {
-		if existingAction == action {
-			return true
-		}
-	}
-
-	return false
+	return slices.Contains(cfg.Actions, action)
 }
 }
 
 
 func (cfg *Config) inlineActionIDExists(action *Action) bool {
 func (cfg *Config) inlineActionIDExists(action *Action) bool {
@@ -400,7 +395,7 @@ func expandEnvTemplate(source string) string {
 		return source
 		return source
 	}
 	}
 	var b strings.Builder
 	var b strings.Builder
-	if err := t.Execute(&b, map[string]interface{}{"Env": env.BuildEnvMap()}); err != nil {
+	if err := t.Execute(&b, map[string]any{"Env": env.BuildEnvMap()}); err != nil {
 		log.WithFields(log.Fields{"error": err}).Debug("Env template execute failed, using literal")
 		log.WithFields(log.Fields{"error": err}).Debug("Env template execute failed, using literal")
 		return source
 		return source
 	}
 	}

+ 4 - 4
service/internal/config/source_file.go

@@ -15,7 +15,7 @@ func stampSourceOnMaps(raw any, sourceFile string) any {
 		return raw
 		return raw
 	}
 	}
 
 
-	items, ok := raw.([]interface{})
+	items, ok := raw.([]any)
 	if !ok {
 	if !ok {
 		return raw
 		return raw
 	}
 	}
@@ -27,7 +27,7 @@ func stampSourceOnMaps(raw any, sourceFile string) any {
 }
 }
 
 
 func stampSourceOnMap(item any, sourceFile string) {
 func stampSourceOnMap(item any, sourceFile string) {
-	m, ok := item.(map[string]interface{})
+	m, ok := item.(map[string]any)
 	if !ok {
 	if !ok {
 		return
 		return
 	}
 	}
@@ -74,7 +74,7 @@ func sourcePathAt(paths []string, index int) string {
 }
 }
 
 
 func stampedSourcePaths(raw any) []string {
 func stampedSourcePaths(raw any) []string {
-	items, ok := raw.([]interface{})
+	items, ok := raw.([]any)
 	if !ok {
 	if !ok {
 		return nil
 		return nil
 	}
 	}
@@ -87,7 +87,7 @@ func stampedSourcePaths(raw any) []string {
 }
 }
 
 
 func stampedSourceFromMap(item any) string {
 func stampedSourceFromMap(item any) string {
-	m, ok := item.(map[string]interface{})
+	m, ok := item.(map[string]any)
 	if !ok {
 	if !ok {
 		return ""
 		return ""
 	}
 	}

+ 1 - 1
service/internal/entities/entities.go

@@ -69,7 +69,7 @@ func watchAndLoadEntity(baseDir string, ef *config.EntityFile) {
 		p = filepath.Join(baseDir, p)
 		p = filepath.Join(baseDir, p)
 		log.WithFields(log.Fields{"entityFile": p}).Debugf("Adding config dir to entity file path")
 		log.WithFields(log.Fields{"entityFile": p}).Debugf("Adding config dir to entity file path")
 	}
 	}
-	go filehelper.WatchFileWrite(p, func(filename string) { loadEntityFile(p, ef.Name) }, filehelper.WatchMeta{
+	go filehelper.WatchFileWrite(p, func(_ string) { loadEntityFile(p, ef.Name) }, filehelper.WatchMeta{
 		ConfigFile: ef.SourceFile,
 		ConfigFile: ef.SourceFile,
 	})
 	})
 	loadEntityFile(p, ef.Name)
 	loadEntityFile(p, ef.Name)

+ 5 - 6
service/internal/entities/storage.go

@@ -10,6 +10,7 @@ package entities
  */
  */
 
 
 import (
 import (
+	"maps"
 	"sort"
 	"sort"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
@@ -39,9 +40,8 @@ func GetEntities() EntitiesByClass {
 	for entityName, entityInstances := range entities {
 	for entityName, entityInstances := range entities {
 		copiedInstances := make(entityInstancesByKey, len(entityInstances))
 		copiedInstances := make(entityInstancesByKey, len(entityInstances))
 
 
-		for key, entity := range entityInstances {
-			copiedInstances[key] = entity
-		}
+		maps.Copy(copiedInstances, entityInstances)
+
 		copiedEntities[entityName] = copiedInstances
 		copiedEntities[entityName] = copiedInstances
 	}
 	}
 
 
@@ -57,9 +57,8 @@ func GetEntityInstances(entityName string) entityInstancesByKey {
 	if entities, ok := entities[entityName]; ok {
 	if entities, ok := entities[entityName]; ok {
 		copiedInstances := make(entityInstancesByKey, len(entities))
 		copiedInstances := make(entityInstancesByKey, len(entities))
 
 
-		for key, entity := range entities {
-			copiedInstances[key] = entity
-		}
+		maps.Copy(copiedInstances, entities)
+
 		return copiedInstances
 		return copiedInstances
 	}
 	}
 
 

+ 4 - 4
service/internal/executor/arguments.go

@@ -67,7 +67,7 @@ func parseExecSegment(arg string, values map[string]string, entity *entities.Ent
 
 
 func validateArguments(values map[string]string, action *config.Action) error {
 func validateArguments(values map[string]string, action *config.Action) error {
 	for _, arg := range action.Arguments {
 	for _, arg := range action.Arguments {
-		if err := typecheckActionArgument(&arg, values[arg.Name], action); err != nil {
+		if err := typecheckActionArgument(&arg, values[arg.Name]); err != nil {
 			return err
 			return err
 		}
 		}
 		log.WithFields(log.Fields{"name": arg.Name, "value": values[arg.Name]}).Debugf("Arg assigned")
 		log.WithFields(log.Fields{"name": arg.Name, "value": values[arg.Name]}).Debugf("Arg assigned")
@@ -90,7 +90,7 @@ func parseActionArguments(req *ExecutionRequest) (string, error) {
 		argName := arg.Name
 		argName := arg.Name
 		argValue := req.Arguments[argName]
 		argValue := req.Arguments[argName]
 
 
-		err := typecheckActionArgument(&arg, argValue, req.Binding.Action)
+		err := typecheckActionArgument(&arg, argValue)
 
 
 		if err != nil {
 		if err != nil {
 			return "", err
 			return "", err
@@ -153,7 +153,7 @@ func argumentSkipsValidation(arg *config.ActionArgument) bool {
 	return arg.Type == "html"
 	return arg.Type == "html"
 }
 }
 
 
-func typecheckActionArgument(arg *config.ActionArgument, value string, action *config.Action) error {
+func typecheckActionArgument(arg *config.ActionArgument, value string) error {
 	if argumentSkipsValidation(arg) {
 	if argumentSkipsValidation(arg) {
 		return nil
 		return nil
 	}
 	}
@@ -199,7 +199,7 @@ func ValidateArgument(arg *config.ActionArgument, value string, action *config.A
 	mangledValue := MangleArgumentValue(arg, value, action.Title)
 	mangledValue := MangleArgumentValue(arg, value, action.Title)
 
 
 	// Use the same validation path as the executor
 	// Use the same validation path as the executor
-	return typecheckActionArgument(arg, mangledValue, action)
+	return typecheckActionArgument(arg, mangledValue)
 }
 }
 
 
 func typecheckActionArgumentFound(value string, arg *config.ActionArgument) error {
 func typecheckActionArgumentFound(value string, arg *config.ActionArgument) error {

+ 87 - 83
service/internal/executor/arguments_test.go

@@ -12,16 +12,17 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 )
 
 
 func TestSanitizeUnsafe(t *testing.T) {
 func TestSanitizeUnsafe(t *testing.T) {
-	assert.Nil(t, TypeSafetyCheck("", "_zomg_ c:/ haxxor ' bobby tables && rm -rf ", "very_dangerous_raw_string"))
+	require.NoError(t, TypeSafetyCheck("", "_zomg_ c:/ haxxor ' bobby tables && rm -rf ", "very_dangerous_raw_string"))
 }
 }
 
 
 func TestSanitizeUnimplemented(t *testing.T) {
 func TestSanitizeUnimplemented(t *testing.T) {
 	err := TypeSafetyCheck("", "I am a happy little argument", "greeting_type")
 	err := TypeSafetyCheck("", "I am a happy little argument", "greeting_type")
 
 
-	assert.NotNil(t, err, "Test an argument type that does not exist")
+	require.Error(t, err, "Test an argument type that does not exist")
 }
 }
 
 
 func TestValidateArgumentCheckboxDefaultValues(t *testing.T) {
 func TestValidateArgumentCheckboxDefaultValues(t *testing.T) {
@@ -35,10 +36,10 @@ func TestValidateArgumentCheckboxDefaultValues(t *testing.T) {
 
 
 	// Default checkbox values without choices should accept "1" and "0"
 	// Default checkbox values without choices should accept "1" and "0"
 	err := ValidateArgument(&arg, "1", &action)
 	err := ValidateArgument(&arg, "1", &action)
-	assert.Nil(t, err, "Expected checkbox value \"1\" to be accepted without choices")
+	require.NoError(t, err, "Expected checkbox value \"1\" to be accepted without choices")
 
 
 	err = ValidateArgument(&arg, "0", &action)
 	err = ValidateArgument(&arg, "0", &action)
-	assert.Nil(t, err, "Expected checkbox value \"0\" to be accepted without choices")
+	require.NoError(t, err, "Expected checkbox value \"0\" to be accepted without choices")
 }
 }
 
 
 func TestMangleCheckboxValueWithChoices(t *testing.T) {
 func TestMangleCheckboxValueWithChoices(t *testing.T) {
@@ -105,14 +106,14 @@ func TestValidateArgumentCheckboxWithChoices(t *testing.T) {
 
 
 	// Titles should be accepted once mangled to their values
 	// Titles should be accepted once mangled to their values
 	err := ValidateArgument(&arg, "Enabled", &action)
 	err := ValidateArgument(&arg, "Enabled", &action)
-	assert.Nil(t, err, "Expected checkbox title \"Enabled\" to be accepted after mangling to choice value")
+	require.NoError(t, err, "Expected checkbox title \"Enabled\" to be accepted after mangling to choice value")
 
 
 	err = ValidateArgument(&arg, "Disabled", &action)
 	err = ValidateArgument(&arg, "Disabled", &action)
-	assert.Nil(t, err, "Expected checkbox title \"Disabled\" to be accepted after mangling to choice value")
+	require.NoError(t, err, "Expected checkbox title \"Disabled\" to be accepted after mangling to choice value")
 
 
 	// Unknown titles should be rejected because they do not match any choice value
 	// Unknown titles should be rejected because they do not match any choice value
 	err = ValidateArgument(&arg, "Maybe", &action)
 	err = ValidateArgument(&arg, "Maybe", &action)
-	assert.NotNil(t, err, "Expected unknown checkbox title to be rejected against choices")
+	require.Error(t, err, "Expected unknown checkbox title to be rejected against choices")
 }
 }
 
 
 func checklistTestArg() config.ActionArgument {
 func checklistTestArg() config.ActionArgument {
@@ -134,13 +135,13 @@ func TestValidateArgumentChecklistSelections(t *testing.T) {
 	action := config.Action{Title: "Test checklist"}
 	action := config.Action{Title: "Test checklist"}
 
 
 	err := ValidateArgument(&arg, "documents", &action)
 	err := ValidateArgument(&arg, "documents", &action)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 
 
 	err = ValidateArgument(&arg, `["documents","photos"]`, &action)
 	err = ValidateArgument(&arg, `["documents","photos"]`, &action)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 
 
 	err = ValidateArgument(&arg, `["documents","unknown"]`, &action)
 	err = ValidateArgument(&arg, `["documents","unknown"]`, &action)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 }
 }
 
 
 func TestValidateArgumentChecklistTitleMangling(t *testing.T) {
 func TestValidateArgumentChecklistTitleMangling(t *testing.T) {
@@ -150,7 +151,7 @@ func TestValidateArgumentChecklistTitleMangling(t *testing.T) {
 	action := config.Action{Title: "Test checklist title mangling"}
 	action := config.Action{Title: "Test checklist title mangling"}
 
 
 	err := ValidateArgument(&arg, `["Documents","Photos"]`, &action)
 	err := ValidateArgument(&arg, `["Documents","Photos"]`, &action)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 }
 }
 
 
 func TestValidateArgumentChecklistEmptySelection(t *testing.T) {
 func TestValidateArgumentChecklistEmptySelection(t *testing.T) {
@@ -160,11 +161,11 @@ func TestValidateArgumentChecklistEmptySelection(t *testing.T) {
 	action := config.Action{Title: "Test checklist empty"}
 	action := config.Action{Title: "Test checklist empty"}
 
 
 	err := ValidateArgument(&arg, "", &action)
 	err := ValidateArgument(&arg, "", &action)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 
 
 	arg.RejectNull = true
 	arg.RejectNull = true
 	err = ValidateArgument(&arg, "", &action)
 	err = ValidateArgument(&arg, "", &action)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 }
 }
 
 
 func TestValidateArgumentChecklistWithoutChoices(t *testing.T) {
 func TestValidateArgumentChecklistWithoutChoices(t *testing.T) {
@@ -177,7 +178,7 @@ func TestValidateArgumentChecklistWithoutChoices(t *testing.T) {
 	action := config.Action{Title: "Test checklist without choices"}
 	action := config.Action{Title: "Test checklist without choices"}
 
 
 	err := ValidateArgument(&arg, "documents", &action)
 	err := ValidateArgument(&arg, "documents", &action)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 }
 }
 
 
 func TestValidateArgumentChecklistRejectsEmptySegment(t *testing.T) {
 func TestValidateArgumentChecklistRejectsEmptySegment(t *testing.T) {
@@ -187,7 +188,7 @@ func TestValidateArgumentChecklistRejectsEmptySegment(t *testing.T) {
 	action := config.Action{Title: "Test checklist empty segment"}
 	action := config.Action{Title: "Test checklist empty segment"}
 
 
 	err := ValidateArgument(&arg, `["documents","","photos"]`, &action)
 	err := ValidateArgument(&arg, `["documents","","photos"]`, &action)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 }
 }
 
 
 func TestMangleArgumentValueChecklist(t *testing.T) {
 func TestMangleArgumentValueChecklist(t *testing.T) {
@@ -223,13 +224,13 @@ func TestValidateArgumentChecklistEntitySelections(t *testing.T) {
 	action := config.Action{Title: "Test checklist entity"}
 	action := config.Action{Title: "Test checklist entity"}
 
 
 	err := ValidateArgument(&arg, "attic", &action)
 	err := ValidateArgument(&arg, "attic", &action)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 
 
 	err = ValidateArgument(&arg, `["attic","basement"]`, &action)
 	err = ValidateArgument(&arg, `["attic","basement"]`, &action)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 
 
 	err = ValidateArgument(&arg, `["attic","unknown"]`, &action)
 	err = ValidateArgument(&arg, `["attic","unknown"]`, &action)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 }
 }
 
 
 func TestMangleArgumentValueChecklistEntityTitles(t *testing.T) {
 func TestMangleArgumentValueChecklistEntityTitles(t *testing.T) {
@@ -274,7 +275,7 @@ func TestParseActionArgumentsChecklistEmptySelection(t *testing.T) {
 	mangleInvalidArgumentValues(req)
 	mangleInvalidArgumentValues(req)
 	out, err := parseActionArguments(req)
 	out, err := parseActionArguments(req)
 
 
-	assert.Nil(t, err)
+	require.NoError(t, err)
 	assert.Equal(t, "echo 'Selected segments: '", out)
 	assert.Equal(t, "echo 'Selected segments: '", out)
 }
 }
 
 
@@ -307,13 +308,13 @@ func TestArgumentValueNullable(t *testing.T) {
 	out, err := parseActionArguments(req)
 	out, err := parseActionArguments(req)
 
 
 	assert.Equal(t, "echo 'Releasing  hounds'", out)
 	assert.Equal(t, "echo 'Releasing  hounds'", out)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 
 
 	req.Binding.Action.Arguments[0].RejectNull = true
 	req.Binding.Action.Arguments[0].RejectNull = true
 
 
 	_, err = parseActionArguments(req)
 	_, err = parseActionArguments(req)
 
 
-	assert.NotNil(t, err)
+	require.Error(t, err)
 }
 }
 
 
 func TestArgumentNameNumbers(t *testing.T) {
 func TestArgumentNameNumbers(t *testing.T) {
@@ -336,7 +337,7 @@ func TestArgumentNameNumbers(t *testing.T) {
 	out, err := parseActionArguments(req)
 	out, err := parseActionArguments(req)
 
 
 	assert.Equal(t, "echo 'Tickling Fred'", out)
 	assert.Equal(t, "echo 'Tickling Fred'", out)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 }
 }
 
 
 func TestArgumentNotProvided(t *testing.T) {
 func TestArgumentNotProvided(t *testing.T) {
@@ -356,8 +357,8 @@ func TestArgumentNotProvided(t *testing.T) {
 
 
 	out, err := parseActionArguments(req)
 	out, err := parseActionArguments(req)
 
 
-	assert.Equal(t, "", out)
-	assert.Equal(t, err.Error(), "required arg not provided: personName")
+	assert.Empty(t, out)
+	assert.Equal(t, "required arg not provided: personName", err.Error())
 }
 }
 
 
 func TestExecArrayParsing(t *testing.T) {
 func TestExecArrayParsing(t *testing.T) {
@@ -372,7 +373,7 @@ func TestExecArrayParsing(t *testing.T) {
 
 
 	out, err := parseActionExec(req.Arguments, req.Binding.Action, req.Binding.Entity)
 	out, err := parseActionExec(req.Arguments, req.Binding.Action, req.Binding.Entity)
 
 
-	assert.Nil(t, err)
+	require.NoError(t, err)
 	assert.Equal(t, []string{"ls", "-alh"}, out)
 	assert.Equal(t, []string{"ls", "-alh"}, out)
 }
 }
 
 
@@ -394,7 +395,7 @@ func TestExecArrayWithTemplateReplacement(t *testing.T) {
 
 
 	out, err := parseActionExec(values, &a1, nil)
 	out, err := parseActionExec(values, &a1, nil)
 
 
-	assert.Nil(t, err)
+	require.NoError(t, err)
 	assert.Equal(t, []string{"ls", "-alh", "tmp"}, out)
 	assert.Equal(t, []string{"ls", "-alh", "tmp"}, out)
 }
 }
 
 
@@ -411,7 +412,7 @@ func TestCheckShellArgumentSafetyWithURL(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "unsafe argument type 'url' cannot be used with Shell execution")
 	assert.Contains(t, err.Error(), "unsafe argument type 'url' cannot be used with Shell execution")
 	assert.Contains(t, err.Error(), "https://docs.olivetin.app/action_execution/shellvsexec.html")
 	assert.Contains(t, err.Error(), "https://docs.olivetin.app/action_execution/shellvsexec.html")
 }
 }
@@ -429,7 +430,7 @@ func TestCheckShellArgumentSafetyWithEmail(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "unsafe argument type 'email' cannot be used with Shell execution")
 	assert.Contains(t, err.Error(), "unsafe argument type 'email' cannot be used with Shell execution")
 }
 }
 
 
@@ -446,7 +447,7 @@ func TestCheckShellArgumentSafetyWithExec(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 }
 }
 
 
 func TestCheckShellArgumentSafetyWithSafeTypes(t *testing.T) {
 func TestCheckShellArgumentSafetyWithSafeTypes(t *testing.T) {
@@ -462,7 +463,7 @@ func TestCheckShellArgumentSafetyWithSafeTypes(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 }
 }
 
 
 func TestCheckShellArgumentSafetyWithPassword(t *testing.T) {
 func TestCheckShellArgumentSafetyWithPassword(t *testing.T) {
@@ -478,7 +479,7 @@ func TestCheckShellArgumentSafetyWithPassword(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "unsafe argument type 'password' cannot be used with Shell execution")
 	assert.Contains(t, err.Error(), "unsafe argument type 'password' cannot be used with Shell execution")
 	assert.Contains(t, err.Error(), "https://docs.olivetin.app/action_execution/shellvsexec.html")
 	assert.Contains(t, err.Error(), "https://docs.olivetin.app/action_execution/shellvsexec.html")
 }
 }
@@ -496,7 +497,7 @@ func TestCheckShellArgumentSafetyWithPasswordAndExec(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 }
 }
 
 
 func TestCheckShellArgumentSafetyWithHTML(t *testing.T) {
 func TestCheckShellArgumentSafetyWithHTML(t *testing.T) {
@@ -509,7 +510,7 @@ func TestCheckShellArgumentSafetyWithHTML(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "unsafe argument type 'html'")
 	assert.Contains(t, err.Error(), "unsafe argument type 'html'")
 }
 }
 
 
@@ -523,7 +524,7 @@ func TestCheckShellArgumentSafetyWithConfirmation(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.Nil(t, err, "confirmation is constrained to 0/1 and is safe with shell")
+	require.NoError(t, err, "confirmation is constrained to 0/1 and is safe with shell")
 }
 }
 
 
 func TestCheckShellArgumentSafetyWithUnnamedConfirmation(t *testing.T) {
 func TestCheckShellArgumentSafetyWithUnnamedConfirmation(t *testing.T) {
@@ -536,7 +537,7 @@ func TestCheckShellArgumentSafetyWithUnnamedConfirmation(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 }
 }
 
 
 func TestCheckShellArgumentSafetyWithChoicelessCheckbox(t *testing.T) {
 func TestCheckShellArgumentSafetyWithChoicelessCheckbox(t *testing.T) {
@@ -549,7 +550,7 @@ func TestCheckShellArgumentSafetyWithChoicelessCheckbox(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "unsafe argument type 'checkbox'")
 	assert.Contains(t, err.Error(), "unsafe argument type 'checkbox'")
 }
 }
 
 
@@ -563,20 +564,20 @@ func TestCheckShellArgumentSafetyWithCustomRegex(t *testing.T) {
 	}
 	}
 
 
 	err := checkShellArgumentSafety(&a1)
 	err := checkShellArgumentSafety(&a1)
-	assert.NotNil(t, err)
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "unsafe argument type 'regex:[a-zA-Z0-9.-]+'")
 	assert.Contains(t, err.Error(), "unsafe argument type 'regex:[a-zA-Z0-9.-]+'")
 }
 }
 
 
 func TestTypeSafetyCheckUrl(t *testing.T) {
 func TestTypeSafetyCheckUrl(t *testing.T) {
-	assert.Nil(t, TypeSafetyCheck("test1", "http://google.com", "url"), "Test URL: google.com")
-	assert.Nil(t, TypeSafetyCheck("test2", "http://technowax.net:80?foo=bar", "url"), "Test URL: technowax.net with query arguments")
-	assert.Nil(t, TypeSafetyCheck("test3", "http://localhost:80?foo=bar", "url"), "Test URL: localhost with query arguments")
-	assert.Nil(t, TypeSafetyCheck("test7", "https://example.com/path", "url"), "Test URL: https scheme")
-	assert.NotNil(t, TypeSafetyCheck("test4", "http://lo  host:80", "url"), "Test a badly formed URL")
-	assert.NotNil(t, TypeSafetyCheck("test5", "12345", "url"), "Test a badly formed URL")
-	assert.NotNil(t, TypeSafetyCheck("test6", "_!23;", "url"), "Test a badly formed URL")
-	assert.NotNil(t, TypeSafetyCheck("test8", "file:///etc/passwd", "url"), "file:// scheme must be rejected")
-	assert.NotNil(t, TypeSafetyCheck("test9", "gopher://example.com", "url"), "gopher:// scheme must be rejected")
+	require.NoError(t, TypeSafetyCheck("test1", "http://google.com", "url"), "Test URL: google.com")
+	require.NoError(t, TypeSafetyCheck("test2", "http://technowax.net:80?foo=bar", "url"), "Test URL: technowax.net with query arguments")
+	require.NoError(t, TypeSafetyCheck("test3", "http://localhost:80?foo=bar", "url"), "Test URL: localhost with query arguments")
+	require.NoError(t, TypeSafetyCheck("test7", "https://example.com/path", "url"), "Test URL: https scheme")
+	require.Error(t, TypeSafetyCheck("test4", "http://lo  host:80", "url"), "Test a badly formed URL")
+	require.Error(t, TypeSafetyCheck("test5", "12345", "url"), "Test a badly formed URL")
+	require.Error(t, TypeSafetyCheck("test6", "_!23;", "url"), "Test a badly formed URL")
+	require.Error(t, TypeSafetyCheck("test8", "file:///etc/passwd", "url"), "file:// scheme must be rejected")
+	require.Error(t, TypeSafetyCheck("test9", "gopher://example.com", "url"), "gopher:// scheme must be rejected")
 }
 }
 
 
 func TestTypeSafetyCheckRegex(t *testing.T) {
 func TestTypeSafetyCheckRegex(t *testing.T) {
@@ -622,9 +623,9 @@ func TestTypeSafetyCheckRegex(t *testing.T) {
 			err := typeSafetyCheckRegex(tt.field, tt.value, tt.pattern)
 			err := typeSafetyCheckRegex(tt.field, tt.value, tt.pattern)
 
 
 			if tt.hasError {
 			if tt.hasError {
-				assert.NotNil(t, err, "Expected error for value %s with pattern %s, but got no error", tt.value, tt.pattern)
+				require.Error(t, err, "Expected error for value %s with pattern %s, but got no error", tt.value, tt.pattern)
 			} else {
 			} else {
-				assert.Nil(t, err, "Expected no error for value %s with pattern %s, but got error: %v", tt.value, tt.pattern, err)
+				require.NoError(t, err, "Expected no error for value %s with pattern %s, but got error: %v", tt.value, tt.pattern, err)
 			}
 			}
 		})
 		})
 	}
 	}
@@ -687,9 +688,9 @@ func TestTypeSafetyCheckEmail(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			err := TypeSafetyCheck(tt.field, tt.value, "email")
 			err := TypeSafetyCheck(tt.field, tt.value, "email")
 			if tt.hasError {
 			if tt.hasError {
-				assert.NotNil(t, err, "Expected error for value '%s'", tt.value)
+				require.Error(t, err, "Expected error for value '%s'", tt.value)
 			} else {
 			} else {
-				assert.Nil(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
+				require.NoError(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
 			}
 			}
 		})
 		})
 	}
 	}
@@ -716,9 +717,9 @@ func TestTypeSafetyCheckDatetime(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			err := TypeSafetyCheck(tt.field, tt.value, "datetime")
 			err := TypeSafetyCheck(tt.field, tt.value, "datetime")
 			if tt.hasError {
 			if tt.hasError {
-				assert.NotNil(t, err, "Expected error for value '%s'", tt.value)
+				require.Error(t, err, "Expected error for value '%s'", tt.value)
 			} else {
 			} else {
-				assert.Nil(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
+				require.NoError(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
 			}
 			}
 		})
 		})
 	}
 	}
@@ -740,7 +741,7 @@ func TestTypeSafetyCheckRawStringMultiline(t *testing.T) {
 	for _, tt := range tests {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			err := TypeSafetyCheck(tt.field, tt.value, "raw_string_multiline")
 			err := TypeSafetyCheck(tt.field, tt.value, "raw_string_multiline")
-			assert.Nil(t, err, "raw_string_multiline should accept any value")
+			require.NoError(t, err, "raw_string_multiline should accept any value")
 		})
 		})
 	}
 	}
 }
 }
@@ -772,6 +773,8 @@ func TestTypeSafetyCheckUnicodeIdentifier(t *testing.T) {
 }
 }
 
 
 func validateTypeSafetyResult(t *testing.T, value string, expectsError bool, err error) {
 func validateTypeSafetyResult(t *testing.T, value string, expectsError bool, err error) {
+	t.Helper()
+
 	if expectsError {
 	if expectsError {
 		assertErrorExpected(t, value, err)
 		assertErrorExpected(t, value, err)
 	} else {
 	} else {
@@ -780,6 +783,8 @@ func validateTypeSafetyResult(t *testing.T, value string, expectsError bool, err
 }
 }
 
 
 func assertErrorExpected(t *testing.T, value string, err error) {
 func assertErrorExpected(t *testing.T, value string, err error) {
+	t.Helper()
+
 	if err == nil {
 	if err == nil {
 		t.Errorf("Expected error for value '%s', but got none", value)
 		t.Errorf("Expected error for value '%s', but got none", value)
 	} else {
 	} else {
@@ -788,6 +793,8 @@ func assertErrorExpected(t *testing.T, value string, err error) {
 }
 }
 
 
 func assertNoErrorExpected(t *testing.T, value string, err error) {
 func assertNoErrorExpected(t *testing.T, value string, err error) {
+	t.Helper()
+
 	if err != nil {
 	if err != nil {
 		t.Errorf("Expected no error for value '%s', but got: %v", value, err)
 		t.Errorf("Expected no error for value '%s', but got: %v", value, err)
 	} else {
 	} else {
@@ -816,9 +823,9 @@ func TestTypeSafetyCheckAsciiIdentifier(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			err := TypeSafetyCheck(tt.field, tt.value, "ascii_identifier")
 			err := TypeSafetyCheck(tt.field, tt.value, "ascii_identifier")
 			if tt.hasError {
 			if tt.hasError {
-				assert.NotNil(t, err, "Expected error for value '%s'", tt.value)
+				require.Error(t, err, "Expected error for value '%s'", tt.value)
 			} else {
 			} else {
-				assert.Nil(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
+				require.NoError(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
 			}
 			}
 		})
 		})
 	}
 	}
@@ -854,9 +861,9 @@ func TestTypeSafetyCheckDnsName(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			err := TypeSafetyCheck("host", tt.value, "dnsname")
 			err := TypeSafetyCheck("host", tt.value, "dnsname")
 			if tt.hasError {
 			if tt.hasError {
-				assert.NotNil(t, err, "Expected error for value '%s'", tt.value)
+				require.Error(t, err, "Expected error for value '%s'", tt.value)
 			} else {
 			} else {
-				assert.Nil(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
+				require.NoError(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
 			}
 			}
 		})
 		})
 	}
 	}
@@ -886,9 +893,9 @@ func TestTypeSafetyCheckShellSafeIdentifier(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			err := TypeSafetyCheck("username", tt.value, "shell_safe_identifier")
 			err := TypeSafetyCheck("username", tt.value, "shell_safe_identifier")
 			if tt.hasError {
 			if tt.hasError {
-				assert.NotNil(t, err, "Expected error for value '%s'", tt.value)
+				require.Error(t, err, "Expected error for value '%s'", tt.value)
 			} else {
 			} else {
-				assert.Nil(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
+				require.NoError(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
 			}
 			}
 		})
 		})
 	}
 	}
@@ -915,9 +922,9 @@ func TestTypeSafetyCheckAsciiSentence(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			err := TypeSafetyCheck(tt.field, tt.value, "ascii_sentence")
 			err := TypeSafetyCheck(tt.field, tt.value, "ascii_sentence")
 			if tt.hasError {
 			if tt.hasError {
-				assert.NotNil(t, err, "Expected error for value '%s'", tt.value)
+				require.Error(t, err, "Expected error for value '%s'", tt.value)
 			} else {
 			} else {
-				assert.Nil(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
+				require.NoError(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
 			}
 			}
 		})
 		})
 	}
 	}
@@ -928,10 +935,9 @@ func TestTypecheckActionArgumentEmptyName(t *testing.T) {
 		Name: "",
 		Name: "",
 		Type: "ascii",
 		Type: "ascii",
 	}
 	}
-	action := config.Action{Title: "Test"}
 
 
-	err := typecheckActionArgument(&arg, "test", &action)
-	assert.NotNil(t, err)
+	err := typecheckActionArgument(&arg, "test")
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "argument name cannot be empty")
 	assert.Contains(t, err.Error(), "argument name cannot be empty")
 }
 }
 
 
@@ -940,17 +946,16 @@ func TestTypecheckActionArgumentConfirmation(t *testing.T) {
 		Name: "confirm",
 		Name: "confirm",
 		Type: "confirmation",
 		Type: "confirmation",
 	}
 	}
-	action := config.Action{Title: "Test"}
 
 
-	assert.Nil(t, typecheckActionArgument(&arg, "0", &action))
-	assert.Nil(t, typecheckActionArgument(&arg, "1", &action))
+	require.NoError(t, typecheckActionArgument(&arg, "0"))
+	require.NoError(t, typecheckActionArgument(&arg, "1"))
 
 
-	err := typecheckActionArgument(&arg, "any_value", &action)
-	assert.NotNil(t, err)
+	err := typecheckActionArgument(&arg, "any_value")
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "must be \"0\" or \"1\"")
 	assert.Contains(t, err.Error(), "must be \"0\" or \"1\"")
 
 
-	err = typecheckActionArgument(&arg, "", &action)
-	assert.NotNil(t, err)
+	err = typecheckActionArgument(&arg, "")
+	require.Error(t, err)
 	assert.Contains(t, err.Error(), "must be \"0\" or \"1\"")
 	assert.Contains(t, err.Error(), "must be \"0\" or \"1\"")
 }
 }
 
 
@@ -959,10 +964,9 @@ func TestTypecheckActionArgumentUnnamedConfirmation(t *testing.T) {
 		Type:  "confirmation",
 		Type:  "confirmation",
 		Title: "Are you sure?!",
 		Title: "Are you sure?!",
 	}
 	}
-	action := config.Action{Title: "Test"}
 
 
-	assert.Nil(t, typecheckActionArgument(&arg, "", &action))
-	assert.Nil(t, typecheckActionArgument(&arg, "ignored", &action))
+	require.NoError(t, typecheckActionArgument(&arg, ""))
+	require.NoError(t, typecheckActionArgument(&arg, "ignored"))
 }
 }
 
 
 func TestTypecheckActionArgumentHtmlWithoutName(t *testing.T) {
 func TestTypecheckActionArgumentHtmlWithoutName(t *testing.T) {
@@ -976,7 +980,7 @@ func TestTypecheckActionArgumentHtmlWithoutName(t *testing.T) {
 	}
 	}
 
 
 	err := validateArguments(map[string]string{}, &action)
 	err := validateArguments(map[string]string{}, &action)
-	assert.NoError(t, err)
+	require.NoError(t, err)
 }
 }
 
 
 func TestParseCommandForReplacements(t *testing.T) {
 func TestParseCommandForReplacements(t *testing.T) {
@@ -1038,12 +1042,12 @@ func TestParseCommandForReplacements(t *testing.T) {
 			output, err := tpl.ParseTemplateWithActionContext(tt.shellCommand, nil, tt.values)
 			output, err := tpl.ParseTemplateWithActionContext(tt.shellCommand, nil, tt.values)
 
 
 			if tt.expectError {
 			if tt.expectError {
-				assert.NotNil(t, err, "Expected error but got none")
+				require.Error(t, err, "Expected error but got none")
 				if tt.errorContains != "" {
 				if tt.errorContains != "" {
 					assert.Contains(t, err.Error(), tt.errorContains)
 					assert.Contains(t, err.Error(), tt.errorContains)
 				}
 				}
 			} else {
 			} else {
-				assert.Nil(t, err, "Expected no error but got: %v", err)
+				require.NoError(t, err, "Expected no error but got: %v", err)
 				assert.Equal(t, tt.expectedOutput, output)
 				assert.Equal(t, tt.expectedOutput, output)
 			}
 			}
 		})
 		})
@@ -1136,10 +1140,10 @@ func TestArgumentChoicesValidation(t *testing.T) {
 			_, err := parseActionArguments(tt.req)
 			_, err := parseActionArguments(tt.req)
 
 
 			if tt.expectError {
 			if tt.expectError {
-				assert.NotNil(t, err, tt.description)
+				require.Error(t, err, tt.description)
 				assert.Contains(t, err.Error(), "predefined choices")
 				assert.Contains(t, err.Error(), "predefined choices")
 			} else {
 			} else {
-				assert.Nil(t, err, tt.description)
+				require.NoError(t, err, tt.description)
 			}
 			}
 		})
 		})
 	}
 	}
@@ -1161,7 +1165,7 @@ func TestTypeSafetyCheckVeryDangerousRawString(t *testing.T) {
 	for _, value := range tests {
 	for _, value := range tests {
 		t.Run(fmt.Sprintf("Value: %s", value), func(t *testing.T) {
 		t.Run(fmt.Sprintf("Value: %s", value), func(t *testing.T) {
 			err := TypeSafetyCheck("test", value, "very_dangerous_raw_string")
 			err := TypeSafetyCheck("test", value, "very_dangerous_raw_string")
-			assert.Nil(t, err, "very_dangerous_raw_string should accept any value including: %s", value)
+			require.NoError(t, err, "very_dangerous_raw_string should accept any value including: %s", value)
 		})
 		})
 	}
 	}
 }
 }
@@ -1186,7 +1190,7 @@ func TestParseActionArgumentsWithEntityPrefix(t *testing.T) {
 
 
 	// Test with entity prefix
 	// Test with entity prefix
 	output, err := parseActionArguments(req)
 	output, err := parseActionArguments(req)
-	assert.Nil(t, err)
+	require.NoError(t, err)
 	assert.Contains(t, output, "testuser")
 	assert.Contains(t, output, "testuser")
 }
 }
 
 
@@ -1227,9 +1231,9 @@ func TestComplexRegexPatterns(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			err := typeSafetyCheckRegex("test", tt.value, tt.pattern)
 			err := typeSafetyCheckRegex("test", tt.value, tt.pattern)
 			if tt.hasError {
 			if tt.hasError {
-				assert.NotNil(t, err)
+				require.Error(t, err)
 			} else {
 			} else {
-				assert.Nil(t, err)
+				require.NoError(t, err)
 			}
 			}
 		})
 		})
 	}
 	}

+ 4 - 9
service/internal/executor/executor.go

@@ -17,10 +17,12 @@ import (
 	"context"
 	"context"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
+	"maps"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"path"
 	"path"
 	"regexp"
 	"regexp"
+	"slices"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
@@ -927,12 +929,7 @@ func keepArgument(name string, definedNames map[string]struct{}) bool {
 }
 }
 
 
 func hasWebhookTag(req *ExecutionRequest) bool {
 func hasWebhookTag(req *ExecutionRequest) bool {
-	for _, tag := range req.Tags {
-		if tag == "webhook" {
-			return true
-		}
-	}
-	return false
+	return slices.Contains(req.Tags, "webhook")
 }
 }
 
 
 var systemArgumentDefinitions = []config.ActionArgument{
 var systemArgumentDefinitions = []config.ActionArgument{
@@ -946,9 +943,7 @@ func injectSystemArgs(req *ExecutionRequest) error {
 		return err
 		return err
 	}
 	}
 
 
-	for name, value := range args {
-		req.Arguments[name] = value
-	}
+	maps.Copy(req.Arguments, args)
 
 
 	return nil
 	return nil
 }
 }

+ 0 - 1
service/internal/executor/executor_unix.go

@@ -1,5 +1,4 @@
 //go:build !windows
 //go:build !windows
-// +build !windows
 
 
 package executor
 package executor
 
 

+ 1 - 1
service/internal/servicehost/log_directory_test.go

@@ -13,7 +13,7 @@ func TestResolveLogDirectory(t *testing.T) {
 	baseDir := filepath.Join(t.TempDir(), "OliveTin")
 	baseDir := filepath.Join(t.TempDir(), "OliveTin")
 	absoluteDir := t.TempDir()
 	absoluteDir := t.TempDir()
 
 
-	assert.Equal(t, "", resolveLogDirectory("", baseDir))
+	assert.Empty(t, resolveLogDirectory("", baseDir))
 	assert.Equal(t, absoluteDir, resolveLogDirectory(absoluteDir, baseDir))
 	assert.Equal(t, absoluteDir, resolveLogDirectory(absoluteDir, baseDir))
 	assert.Equal(t, filepath.Join(baseDir, "logs", "service"), resolveLogDirectory("./logs/service", baseDir))
 	assert.Equal(t, filepath.Join(baseDir, "logs", "service"), resolveLogDirectory("./logs/service", baseDir))
 	assert.Equal(t, "logs/service", resolveLogDirectory("logs/service", ""))
 	assert.Equal(t, "logs/service", resolveLogDirectory("logs/service", ""))

+ 0 - 1
service/internal/servicehost/servicehost_nonwin.go

@@ -1,5 +1,4 @@
 //go:build !windows
 //go:build !windows
-// +build !windows
 
 
 package servicehost
 package servicehost
 
 

+ 1 - 1
service/internal/tpl/templates.go

@@ -44,7 +44,7 @@ type generalTemplateContext struct {
 }
 }
 
 
 type actionTemplateContext struct {
 type actionTemplateContext struct {
-	CurrentEntity interface{}
+	CurrentEntity any
 	Arguments     map[string]string
 	Arguments     map[string]string
 
 
 	// These are deliberately repeated because embedding structs
 	// These are deliberately repeated because embedding structs

+ 1 - 1
service/internal/updatecheck/updateCheck.go

@@ -90,7 +90,7 @@ func doRequest() string {
 
 
 	defer cancel()
 	defer cancel()
 
 
-	req, err := http.NewRequestWithContext(ctx, "GET", "http://update-check.olivetin.app/versions.json", nil)
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://update-check.olivetin.app/versions.json", nil)
 
 
 	if err != nil {
 	if err != nil {
 		log.Errorf("Update check failed %v", err)
 		log.Errorf("Update check failed %v", err)

+ 3 - 3
service/internal/webhooks/jsonpath.go

@@ -8,11 +8,11 @@ import (
 )
 )
 
 
 type JSONMatcher struct {
 type JSONMatcher struct {
-	payload interface{}
+	payload any
 }
 }
 
 
 func NewJSONMatcher(payload []byte) (*JSONMatcher, error) {
 func NewJSONMatcher(payload []byte) (*JSONMatcher, error) {
-	var data interface{}
+	var data any
 	if err := json.Unmarshal(payload, &data); err != nil {
 	if err := json.Unmarshal(payload, &data); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -60,6 +60,6 @@ func (m *JSONMatcher) ExtractValue(pathExpr string) (string, error) {
 	return string(jsonBytes), nil
 	return string(jsonBytes), nil
 }
 }
 
 
-func (m *JSONMatcher) GetPayload() interface{} {
+func (m *JSONMatcher) GetPayload() any {
 	return m.payload
 	return m.payload
 }
 }

+ 1 - 2
service/internal/webhooks/matcher.go

@@ -123,8 +123,7 @@ func (m *WebhookMatcher) matchPathValue(matcher *JSONMatcher, jsonPath, expected
 }
 }
 
 
 func (m *WebhookMatcher) compareValues(actual, expected string) bool {
 func (m *WebhookMatcher) compareValues(actual, expected string) bool {
-	if strings.HasPrefix(expected, "regex:") {
-		pattern := strings.TrimPrefix(expected, "regex:")
+	if pattern, hasRegex := strings.CutPrefix(expected, "regex:"); hasRegex {
 		matched, err := regexp.MatchString(pattern, actual)
 		matched, err := regexp.MatchString(pattern, actual)
 		if err != nil {
 		if err != nil {
 			log.WithFields(log.Fields{
 			log.WithFields(log.Fields{

+ 1 - 1
service/main.go

@@ -158,7 +158,7 @@ func configPathExists(configPath string) bool {
 }
 }
 
 
 func watchConfigFile(k *koanf.Koanf, f *file.File, configPath string) {
 func watchConfigFile(k *koanf.Koanf, f *file.File, configPath string) {
-	err := f.Watch(func(evt interface{}, err error) {
+	err := f.Watch(func(evt any, err error) {
 		log.Infof("config file changed: %v", evt)
 		log.Infof("config file changed: %v", evt)
 
 
 		errLoad := k.Load(f, yaml.Parser())
 		errLoad := k.Load(f, yaml.Parser())