{"record":{"id":"2624cafa0fae4b8d","repo":"juanfont/headscale","slug":"test-entry-must-have-at-least-one-of-accept-or","errorCode":null,"errorMessage":"test entry must have at least one of \"accept\" or \"deny\"","messagePattern":"test entry must have at least one of \"accept\" or \"deny\"","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":147,"sourceCode":"\tErrInvalidACLAction            = errors.New(\"invalid ACL action\")\n\tErrInvalidSSHAction            = errors.New(\"invalid SSH action\")\n\tErrInvalidProtocolNumber       = errors.New(\"invalid protocol number\")\n\tErrProtocolLeadingZero         = errors.New(\"leading 0 not permitted in protocol number\")\n\tErrProtocolOutOfRange          = errors.New(\"protocol number out of range (0-255)\")\n\tErrAutogroupNotSupported       = errors.New(\"autogroup not supported in headscale\")\n\tErrAutogroupInternetSrc        = errors.New(\"autogroup:internet can only be used in ACL destinations\")\n\tErrAutogroupSelfSrc            = errors.New(\"\\\"autogroup:self\\\" not valid on the src side of a rule\")\n\tErrAutogroupNotSupportedACLSrc = errors.New(\"autogroup not supported for ACL sources\")\n\tErrAutogroupNotSupportedACLDst = errors.New(\"autogroup not supported for ACL destinations\")\n\tErrAutogroupDangerAllDst       = errors.New(\"cannot use autogroup:danger-all as a dst\")\n\tErrAutogroupNotSupportedSSHSrc = errors.New(\"autogroup not supported for SSH sources\")\n\tErrAutogroupNotSupportedSSHDst = errors.New(\"autogroup not supported for SSH destinations\")\n\tErrHostNotDefined              = errors.New(\"host not defined in policy\")\n\tErrSSHSourceAliasNotSupported  = errors.New(\"alias not supported for SSH source\")\n\tErrSSHDestAliasNotSupported    = errors.New(\"alias not supported for SSH destination\")\n\tErrUnknownField                = errors.New(\"unknown field\")\n\tErrProtocolNoSpecificPorts     = errors.New(\"protocol does not support specific ports\")\n\tErrTestEmptyAssertions         = errors.New(\"test entry must have at least one of \\\"accept\\\" or \\\"deny\\\"\")\n\tErrTestProtocolNotAllowed      = errors.New(\"test protocol must be tcp, udp, sctp, or empty\")\n\tErrTestDestinationMultiPort    = errors.New(\"test destination port must be a single port\")\n\tErrTestDestinationCIDR         = errors.New(\"test destination must be a single host, not a CIDR range\")\n\tErrAutogroupInternetTestDst    = errors.New(\"autogroup:internet not valid as a test destination\")\n\tErrSSHTestEmptySrc             = errors.New(\"SSH tests entry must have a non-empty src\")\n\tErrSSHTestEmptyDst             = errors.New(\"SSH tests entry must have at least one dst\")\n\tErrSSHTestDstUnknownTag        = errors.New(\"SSH tests dst contains unknown tag\")\n\tErrSSHTestDstDisallowedElement = errors.New(\"SSH tests dst contains disallowed element\")\n)\n\ntype resolved struct {\n\tips netipx.IPSet\n}\n\nfunc newResolved(ipb *netipx.IPSetBuilder) (resolved, error) {\n\tips, err := ipb.IPSet()\n\tif err != nil {\n\t\treturn resolved{}, err","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L129-L165","documentation":"ErrTestEmptyAssertions is returned by validateTests (hscontrol/policy/v2/types.go:3202) when a policy \"tests\" entry declares neither an \"accept\" nor a \"deny\" list (both empty). A test entry must assert an expected outcome for at least one destination, otherwise it verifies nothing. It is collected into a multi-error wrapped by errPolicyTestsFailed with the test's index.","triggerScenarios":"A tests block entry containing only src/proto, e.g. {\"src\": \"user1\", \"proto\": \"tcp\", \"accept\": []} or an entry with both accept and deny keys omitted entirely. Raised during policy validation when len(t.Accept) == 0 && len(t.Deny) == 0.","commonSituations":"Writing a first tests entry and forgetting the assertion half; YAML-to-HuJSON conversion dropping empty lists; commenting out assertions while debugging and forgetting to restore them; templates that scaffold {\"src\": ..., \"accept\": []}.","solutions":["Add at least one destination to \"accept\" or \"deny\" in the flagged test entry (index is in the message)","If the entry was scaffolding, delete it entirely","Re-run policy validation to confirm no further test-shape errors follow (they are reported together)"],"exampleFix":"// before\n\"tests\": [{\"src\": \"user1\", \"proto\": \"tcp\"}]\n// after\n\"tests\": [{\"src\": \"user1\", \"proto\": \"tcp\", \"accept\": [\"tag:server:22\"]}]","handlingStrategy":"validation","validationCode":"// Validate a tests entry shape before adding it to the policy object\nfunc testEntryHasAssertion(t PolicyTest) bool {\n    return len(t.Accept) > 0 || len(t.Deny) > 0\n}","typeGuard":null,"tryCatchPattern":"if err := policyv2.LoadPolicy(buf); err != nil {\n    if errors.Is(err, policyv2.ErrTestEmptyAssertions) {\n        // remove or complete the flagged tests entry (index in message)\n    }\n}","preventionTips":["Treat tests entries as incomplete until they have src plus accept or deny","Never merge scaffolding entries with empty assertion lists","Run policy validation on every policy edit, not only on deploy"],"tags":["policy","tests","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}