diff --git a/go/declaration/adoption_test.go b/go/declaration/adoption_test.go index e422516..1889643 100644 --- a/go/declaration/adoption_test.go +++ b/go/declaration/adoption_test.go @@ -16,7 +16,7 @@ const adoptedResources = `"resources":[ const sixtyFour = "0000000000000000000000000000000000000000000000000000000000000000" -func TestAnAdoptionIsReadWithTheDeclaration(t *testing.T) { +func testAnAdoptionIsReadWithTheDeclaration(t *testing.T) { d, err := Parse([]byte(`{"adoption":{"taken":["postgres"],"untaken":{"hello-web":["hello-web.page","hello-web.server"]}}, "declaration":1,` + adoptedResources + `}`)) if err != nil { @@ -36,7 +36,7 @@ func TestAnAdoptionIsReadWithTheDeclaration(t *testing.T) { } } -func TestADeclarationWithNoAdoptionIsConverged(t *testing.T) { +func testADeclarationWithNoAdoptionIsConverged(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,` + adoptedResources + `}`)) if err != nil { t.Fatal(err) @@ -49,7 +49,7 @@ func TestADeclarationWithNoAdoptionIsConverged(t *testing.T) { } } -func TestAnAdoptionNamingAnUnknownIDIsRefused(t *testing.T) { +func testAnAdoptionNamingAnUnknownIDIsRefused(t *testing.T) { refusal := refusalFor(t, `{"adoption":{"taken":[],"untaken":{"hello-web":["hello-web.missing"]}}, "declaration":1,`+adoptedResources+`}`) if !strings.Contains(strings.Join(refusal.Problems, "\n"), "hello-web.missing") { @@ -57,7 +57,7 @@ func TestAnAdoptionNamingAnUnknownIDIsRefused(t *testing.T) { } } -func TestAnAdoptionMayNameAResourceOfAnyKind(t *testing.T) { +func testAnAdoptionMayNameAResourceOfAnyKind(t *testing.T) { // A directory, a service or an action can reach what was found as surely as a file can, so // the controller lists every resource of an untaken module (novox/hq ADR 0103). d, err := Parse([]byte(`{"adoption":{"taken":[],"untaken":{"hello-web":["hello-web.data"]}}, @@ -70,7 +70,7 @@ func TestAnAdoptionMayNameAResourceOfAnyKind(t *testing.T) { } } -func TestAnIDUnderTwoModulesIsRefused(t *testing.T) { +func testAnIDUnderTwoModulesIsRefused(t *testing.T) { refusal := refusalFor(t, `{"adoption":{"taken":[],"untaken":{"a":["hello-web.page"],"b":["hello-web.page"]}}, "declaration":1,`+adoptedResources+`}`) if !strings.Contains(strings.Join(refusal.Problems, "\n"), "both") { @@ -78,7 +78,7 @@ func TestAnIDUnderTwoModulesIsRefused(t *testing.T) { } } -func TestAModuleBothTakenAndUntakenIsRefused(t *testing.T) { +func testAModuleBothTakenAndUntakenIsRefused(t *testing.T) { refusal := refusalFor(t, `{"adoption":{"taken":["hello-web"],"untaken":{"hello-web":["hello-web.page"]}}, "declaration":1,`+adoptedResources+`}`) if !strings.Contains(strings.Join(refusal.Problems, "\n"), "both taken and untaken") { @@ -86,7 +86,7 @@ func TestAModuleBothTakenAndUntakenIsRefused(t *testing.T) { } } -func TestTheMeshsOwnResourcesAreNeverUntaken(t *testing.T) { +func testTheMeshsOwnResourcesAreNeverUntaken(t *testing.T) { refusal := refusalFor(t, `{"adoption":{"taken":[],"untaken":{"x":["adoption.guard"]}}, "declaration":1,"resources":[ {"id":"adoption.guard","type":"file","path":"/etc/mesh/guard.nft","content":"x"}]}`) @@ -95,11 +95,11 @@ func TestTheMeshsOwnResourcesAreNeverUntaken(t *testing.T) { } } -func TestAnAdoptionWithAnUnknownFieldIsRefused(t *testing.T) { +func testAnAdoptionWithAnUnknownFieldIsRefused(t *testing.T) { refusalFor(t, `{"adoption":{"taken":[],"held":["x"]},"declaration":1,`+adoptedResources+`}`) } -func TestACarriedBundleCannotSayTheNodeIsAdopted(t *testing.T) { +func testACarriedBundleCannotSayTheNodeIsAdopted(t *testing.T) { _, err := ParseTrusted([]byte(`{"adoption":{"taken":[]},"declaration":1,` + adoptedResources + `}`)) if err == nil || !strings.Contains(err.Error(), "only the mesh can say") { t.Fatalf("a bundle claiming adoption was not refused: %v", err) @@ -107,7 +107,7 @@ func TestACarriedBundleCannotSayTheNodeIsAdopted(t *testing.T) { } // novox/hq ADR 0105: a service may take over a found tunnel, said whole and on an adopted node. -func TestTakingOverATunnelIsSaidWholeAndForARunningService(t *testing.T) { +func testTakingOverATunnelIsSaidWholeAndForARunningService(t *testing.T) { adoptedWith := func(service string) error { _, err := Parse([]byte(`{"declaration":1,"adoption":{"taken":[]},"resources":[` + service + `]}`)) return err @@ -130,3 +130,17 @@ func TestTakingOverATunnelIsSaidWholeAndForARunningService(t *testing.T) { } } } + +// TestAdoption holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestAdoption(t *testing.T) { + t.Run("AnAdoptionIsReadWithTheDeclaration", testAnAdoptionIsReadWithTheDeclaration) + t.Run("ADeclarationWithNoAdoptionIsConverged", testADeclarationWithNoAdoptionIsConverged) + t.Run("AnAdoptionNamingAnUnknownIDIsRefused", testAnAdoptionNamingAnUnknownIDIsRefused) + t.Run("AnAdoptionMayNameAResourceOfAnyKind", testAnAdoptionMayNameAResourceOfAnyKind) + t.Run("AnIDUnderTwoModulesIsRefused", testAnIDUnderTwoModulesIsRefused) + t.Run("AModuleBothTakenAndUntakenIsRefused", testAModuleBothTakenAndUntakenIsRefused) + t.Run("TheMeshsOwnResourcesAreNeverUntaken", testTheMeshsOwnResourcesAreNeverUntaken) + t.Run("AnAdoptionWithAnUnknownFieldIsRefused", testAnAdoptionWithAnUnknownFieldIsRefused) + t.Run("ACarriedBundleCannotSayTheNodeIsAdopted", testACarriedBundleCannotSayTheNodeIsAdopted) + t.Run("TakingOverATunnelIsSaidWholeAndForARunningService", testTakingOverATunnelIsSaidWholeAndForARunningService) +} diff --git a/go/declaration/block_test.go b/go/declaration/block_test.go index 1512a00..9a726ed 100644 --- a/go/declaration/block_test.go +++ b/go/declaration/block_test.go @@ -8,7 +8,7 @@ import ( // Defends novox/hq issue 128: a file written into a block carries only its lines, in content, // never a line the host keeps as its own marker, and says where a new region goes only as a block. -func TestAFileWrittenIntoABlockIsRefusedUnlessItIsOnlyItsLines(t *testing.T) { +func testAFileWrittenIntoABlockIsRefusedUnlessItIsOnlyItsLines(t *testing.T) { for name, c := range map[string]struct{ resource, refusal string }{ "a begin marker in content": {`{"id":"f","type":"file","path":"/etc/hosts","into":"block","content":"a\n# BEGIN mesh f\nb\n"}`, "# BEGIN mesh f"}, "an end marker in content": {`{"id":"f","type":"file","path":"/etc/hosts","into":"block","content":"a\n# END mesh other\n"}`, "# END mesh other"}, @@ -30,7 +30,7 @@ func TestAFileWrittenIntoABlockIsRefusedUnlessItIsOnlyItsLines(t *testing.T) { } } -func TestAFileWrittenIntoABlockIsRead(t *testing.T) { +func testAFileWrittenIntoABlockIsRead(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"mesh-wireguard.fact-node-names","type":"file","path":"/etc/hosts","into":"block","content":"10.42.0.1 ace\n# BEGIN devtool x\n"}, {"id":"dhcpcd.options","type":"file","path":"/etc/dhcpcd.conf","into":"block","content":"nohook resolv.conf\n","at":"start"}, @@ -50,3 +50,9 @@ func TestAFileWrittenIntoABlockIsRead(t *testing.T) { t.Errorf("the markers are %q and %q", begin, end) } } + +// TestBlock holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestBlock(t *testing.T) { + t.Run("AFileWrittenIntoABlockIsRefusedUnlessItIsOnlyItsLines", testAFileWrittenIntoABlockIsRefusedUnlessItIsOnlyItsLines) + t.Run("AFileWrittenIntoABlockIsRead", testAFileWrittenIntoABlockIsRead) +} diff --git a/go/declaration/cron_test.go b/go/declaration/cron_test.go index 51e3dc0..9340c91 100644 --- a/go/declaration/cron_test.go +++ b/go/declaration/cron_test.go @@ -9,7 +9,7 @@ import ( // arrival and computes the next due minute the scheduler fires on (novox/hq ADR 0053). These test // the parsing and the evaluation; the firing is tested against the scheduler. -func TestParseCronRefusesMalformed(t *testing.T) { +func testParseCronRefusesMalformed(t *testing.T) { for _, expr := range []string{ "", // nothing "* * * *", // four fields @@ -31,7 +31,7 @@ func TestParseCronRefusesMalformed(t *testing.T) { } } -func TestParseCronAcceptsTheOrdinaryForms(t *testing.T) { +func testParseCronAcceptsTheOrdinaryForms(t *testing.T) { for _, expr := range []string{ "* * * * *", // every minute "0 3 * * *", // 03:00 daily @@ -47,7 +47,7 @@ func TestParseCronAcceptsTheOrdinaryForms(t *testing.T) { } } -func TestCronNextIsTheNextMatchingMinute(t *testing.T) { +func testCronNextIsTheNextMatchingMinute(t *testing.T) { // A Monday at 12:00:30, so "next" must round up to a whole minute and land on the first match // strictly after now — never re-firing the minute we are already in. now := time.Date(2026, 9, 7, 12, 0, 30, 0, time.UTC) // 2026-09-07 is a Monday @@ -77,7 +77,7 @@ func TestCronNextIsTheNextMatchingMinute(t *testing.T) { } } -func TestCronDayFieldsAreOredWhenBothRestricted(t *testing.T) { +func testCronDayFieldsAreOredWhenBothRestricted(t *testing.T) { // The standard cron surprise: when day-of-month and day-of-week are both set, a day matches if // EITHER does. "1 * 13 * 5" is due on the 13th OR on any Friday. 2026-09-07 is a Monday the 7th // — neither — and 2026-09-11 is a Friday, and 2026-11-13 is the 13th. @@ -98,3 +98,11 @@ func TestCronDayFieldsAreOredWhenBothRestricted(t *testing.T) { t.Error("a Monday the 7th matched a cron restricted to the 13th OR Friday") } } + +// TestCron holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestCron(t *testing.T) { + t.Run("ParseCronRefusesMalformed", testParseCronRefusesMalformed) + t.Run("ParseCronAcceptsTheOrdinaryForms", testParseCronAcceptsTheOrdinaryForms) + t.Run("CronNextIsTheNextMatchingMinute", testCronNextIsTheNextMatchingMinute) + t.Run("CronDayFieldsAreOredWhenBothRestricted", testCronDayFieldsAreOredWhenBothRestricted) +} diff --git a/go/declaration/declaration_test.go b/go/declaration/declaration_test.go index 36063d4..8b3354e 100644 --- a/go/declaration/declaration_test.go +++ b/go/declaration/declaration_test.go @@ -30,7 +30,7 @@ func refusalFor(t *testing.T, raw string) *RefusalError { return refusal } -func TestAValidDeclarationParsesInOrder(t *testing.T) { +func testAValidDeclarationParsesInOrder(t *testing.T) { d, err := Parse([]byte(valid())) if err != nil { t.Fatalf("unexpected refusal: %v", err) @@ -45,7 +45,7 @@ func TestAValidDeclarationParsesInOrder(t *testing.T) { } } -func TestAnUnknownTypeRefusesTheWholeDeclaration(t *testing.T) { +func testAnUnknownTypeRefusesTheWholeDeclaration(t *testing.T) { // The property everything else rests on. A host that skipped what it did not understand // would apply most of a declaration and report success — a node that looks configured and // is not, which is 04-ISSUES/003 with the declaration on the other side of the wire. @@ -64,7 +64,7 @@ func TestAnUnknownTypeRefusesTheWholeDeclaration(t *testing.T) { } } -func TestAnUnknownFieldIsRefused(t *testing.T) { +func testAnUnknownFieldIsRefused(t *testing.T) { // A field the host does not know is a thing the control plane believes it asked for. refusal := refusalFor(t, `{"declaration":1,"resources":[ {"id":"conf","type":"file","path":"/etc/x","content":"a","immutable":true} @@ -74,7 +74,7 @@ func TestAnUnknownFieldIsRefused(t *testing.T) { } } -func TestAFieldTheTypeDoesNotUseIsRefusedNotIgnored(t *testing.T) { +func testAFieldTheTypeDoesNotUseIsRefusedNotIgnored(t *testing.T) { // The same fault in miniature: set and ignored means the control plane believes it asked // for something the host will never do. refusal := refusalFor(t, `{"declaration":1,"resources":[ @@ -86,7 +86,7 @@ func TestAFieldTheTypeDoesNotUseIsRefusedNotIgnored(t *testing.T) { } } -func TestAnUnknownVersionIsRefusedWhole(t *testing.T) { +func testAnUnknownVersionIsRefusedWhole(t *testing.T) { // An older host handed a newer vocabulary must not quietly do half of it. refusal := refusalFor(t, `{"declaration":99,"resources":[ {"id":"a","type":"directory","path":"/etc/mesh"} @@ -102,7 +102,7 @@ func TestAnUnknownVersionIsRefusedWhole(t *testing.T) { } } -func TestEveryProblemIsReportedAtOnce(t *testing.T) { +func testEveryProblemIsReportedAtOnce(t *testing.T) { // A declaration is generated, so a person reading a refusal is debugging the generator. // Fixing one problem at a time and re-running to find the next wastes their afternoon. refusal := refusalFor(t, `{"declaration":1,"resources":[ @@ -115,7 +115,7 @@ func TestEveryProblemIsReportedAtOnce(t *testing.T) { } } -func TestIdentityIsRequiredAndUnique(t *testing.T) { +func testIdentityIsRequiredAndUnique(t *testing.T) { // Identity is what lets the store know this is the same resource it applied last time, // which is what makes removal possible at all. refusal := refusalFor(t, `{"declaration":1,"resources":[ @@ -127,7 +127,7 @@ func TestIdentityIsRequiredAndUnique(t *testing.T) { } } -func TestModeIsRefusedUnlessItMeansWhatItLooksLike(t *testing.T) { +func testModeIsRefusedUnlessItMeansWhatItLooksLike(t *testing.T) { // "644" and "0644" differ, and the one that looks right in a manifest is the four-digit // form. Accepting both would make a mode mean two things. for _, mode := range []string{"644", "0999", "rwxr-xr-x", "07777777"} { @@ -145,7 +145,7 @@ func TestModeIsRefusedUnlessItMeansWhatItLooksLike(t *testing.T) { } } -func TestARefusalSaysNothingWasApplied(t *testing.T) { +func testARefusalSaysNothingWasApplied(t *testing.T) { // The reader's first question is whether the machine was left half-changed. refusal := refusalFor(t, `{"declaration":1,"resources":[{"id":"x","type":"nope"}]}`) if !strings.Contains(refusal.Error(), "none of it was applied") { @@ -153,13 +153,13 @@ func TestARefusalSaysNothingWasApplied(t *testing.T) { } } -func TestAnEmptyDeclarationIsAMistake(t *testing.T) { +func testAnEmptyDeclarationIsAMistake(t *testing.T) { refusalFor(t, `{"declaration":1,"resources":[]}`) } // --- the vocabulary the foundation bootstrap needs (novox/hq 07-the-foundation.md) --- -func TestAnActionOverTheLinkIsRefused(t *testing.T) { +func testAnActionOverTheLinkIsRefused(t *testing.T) { // novox/hq ADR 0005. The link may push declarations of known shape and never a command to // run. This is the boundary the whole security argument rests on, so it is asserted // directly rather than inferred from the type list. @@ -179,7 +179,7 @@ func TestAnActionOverTheLinkIsRefused(t *testing.T) { } } -func TestAnActionWithoutVerifyIsRefused(t *testing.T) { +func testAnActionWithoutVerifyIsRefused(t *testing.T) { // An action that runs and reports success without reading anything back is the fault this // host exists to prevent. Verify is also the idempotency check, so an action without one // cannot be applied twice safely either. @@ -194,7 +194,7 @@ func TestAnActionWithoutVerifyIsRefused(t *testing.T) { } } -func TestAnImageMustBePinnedByDigest(t *testing.T) { +func testAnImageMustBePinnedByDigest(t *testing.T) { // novox/hq ADR 0006: reproducibility comes from pinning the identity of a thing. A bundle // naming a tag pins nothing — it names whatever that tag points at on the day it runs. for _, image := range []string{ @@ -226,7 +226,7 @@ func TestAnImageMustBePinnedByDigest(t *testing.T) { // can start. The mesh's own control plane is built from source and lives in no public registry; a // machine that built it, or was handed it, names it by what it is — a content address, not a name, // and immutable in exactly the way the rule asks for. -func TestAnImageTheMachineHoldsIsNamedByItsOwnDigest(t *testing.T) { +func testAnImageTheMachineHoldsIsNamedByItsOwnDigest(t *testing.T) { held := "sha256:" + strings.Repeat("b", 64) if _, err := ParseTrusted([]byte(`{"declaration":1,"resources":[ {"id":"control","type":"container","name":"mesh-controller","image":"` + held + `"} @@ -245,7 +245,7 @@ func TestAnImageTheMachineHoldsIsNamedByItsOwnDigest(t *testing.T) { } } -func TestAFieldTheNewTypesDoNotUseIsRefused(t *testing.T) { +func testAFieldTheNewTypesDoNotUseIsRefused(t *testing.T) { // The field-set check must cover the types added last, not only the three it was written // for. A package that carries a `content` is a control plane believing it asked for // something that will never happen. @@ -266,7 +266,7 @@ func TestAFieldTheNewTypesDoNotUseIsRefused(t *testing.T) { } } -func TestTheVocabularyIsTheTwelveShapesTheMeshNeeds(t *testing.T) { +func testTheVocabularyIsTheTwelveShapesTheMeshNeeds(t *testing.T) { // Six of them the bootstrap uses (novox/hq 07-the-foundation.md), and removing one is a // failing test rather than a discovery during a first-node install. // @@ -320,7 +320,7 @@ func TestTheVocabularyIsTheTwelveShapesTheMeshNeeds(t *testing.T) { } } -func TestADeclarationFromDiskMayBeAnnotated(t *testing.T) { +func testADeclarationFromDiskMayBeAnnotated(t *testing.T) { // The bundle in this repository is mostly explanation of why each digest is what it is, and // it could be built into a binary and not applied from disk — two readers for one file. The // failure was `invalid character '/'`, which names the symptom and not the cause. @@ -346,7 +346,7 @@ func TestADeclarationFromDiskMayBeAnnotated(t *testing.T) { } } -func TestSomethingInsideAValueIsNotAComment(t *testing.T) { +func testSomethingInsideAValueIsNotAComment(t *testing.T) { // Every image reference has a `//` in it somewhere near. Only whole lines are dropped. d, err := ParseFileTrusted([]byte(`{"declaration":1,"resources":[ {"id":"f","type":"file","path":"/etc/x","content":"see https://example.invalid/ for why"}]}`)) @@ -370,7 +370,7 @@ func TestSomethingInsideAValueIsNotAComment(t *testing.T) { // // Not hypothetical: a test harness appended a line to the foundation bundle by accident, every // apply kept working, and nothing said so for the entire time it was wrong. -func TestSomethingAfterTheDeclarationIsRefused(t *testing.T) { +func testSomethingAfterTheDeclarationIsRefused(t *testing.T) { good := `{"declaration":1,"resources":[{"id":"a","type":"file","path":"/tmp/a",` + `"content":"x","mode":"0644"}]}` if _, err := ParseFileTrusted([]byte(good)); err != nil { @@ -394,7 +394,7 @@ func TestSomethingAfterTheDeclarationIsRefused(t *testing.T) { } // A name that would read as a reference to something else is refused before it reaches a runtime. -func TestANetworkNameIsRefusedIfItIsNotOne(t *testing.T) { +func testANetworkNameIsRefusedIfItIsNotOne(t *testing.T) { for _, name := range []string{"", "mail/private", "host:mail", "a b"} { refusal := refusalFor(t, `{"declaration":1,"resources":[ {"id":"private","type":"network","name":"`+name+`"} @@ -409,7 +409,7 @@ func TestANetworkNameIsRefusedIfItIsNotOne(t *testing.T) { // // The alternative is a configuration file containing the literal `${secret:x}`, which the program // reads as a value and fails on somewhere with no connection to this. -func TestAFileAskingForASecretItWasNotGivenIsRefused(t *testing.T) { +func testAFileAskingForASecretItWasNotGivenIsRefused(t *testing.T) { refusal := refusalFor(t, `{"declaration":1,"resources":[ {"id":"c","type":"file","path":"/tmp/x","content":"token=${secret:absent}"} ]}`) @@ -419,7 +419,7 @@ func TestAFileAskingForASecretItWasNotGivenIsRefused(t *testing.T) { } // And a secret nobody asked for, because somebody believes it is in a file where it is not. -func TestASecretTheContentNeverUsesIsRefused(t *testing.T) { +func testASecretTheContentNeverUsesIsRefused(t *testing.T) { refusal := refusalFor(t, `{"declaration":1,"resources":[ {"id":"c","type":"file","path":"/tmp/x","content":"nothing here","secrets":{"spare":"S"}} ]}`) @@ -431,7 +431,7 @@ func TestASecretTheContentNeverUsesIsRefused(t *testing.T) { // The runtime's resolver and address flags take only addresses; a name would be refused at // create, after the old container was already gone. Refused on arrival instead — and an address // without a user-defined network is refused for the same reason. -func TestAContainersResolverAndAddressAreAddressesOrRefused(t *testing.T) { +func testAContainersResolverAndAddressAreAddressesOrRefused(t *testing.T) { refused := func(body string) []string { _, err := Parse([]byte(`{"declaration":1,"resources":[` + body + `]}`)) if err == nil { @@ -452,7 +452,7 @@ func TestAContainersResolverAndAddressAreAddressesOrRefused(t *testing.T) { } } -func TestAnExplicitlyEmptyDeclarationIsAccepted(t *testing.T) { +func testAnExplicitlyEmptyDeclarationIsAccepted(t *testing.T) { // A deliberately-empty declaration (novox/hq issue 127) says owns_nothing, and is applied so // the node drops what it last held — distinct from an accidental empty body, which is refused. if _, err := Parse([]byte(`{"declaration":1,"owns_nothing":true,"resources":[]}`)); err != nil { @@ -467,7 +467,7 @@ func TestAnExplicitlyEmptyDeclarationIsAccepted(t *testing.T) { // A container's kept networks are names, not its own network, and not for a step (novox/hq ADR // 0163, rule 4); and the mesh may say which modules it left out, which a carried bundle may not. -func TestKeptNetworksAndLeftOutModulesAreReadStrictly(t *testing.T) { +func testKeptNetworksAndLeftOutModulesAreReadStrictly(t *testing.T) { pinnedImage := "postgres@sha256:" + strings.Repeat("a", 64) d, err := Parse([]byte(`{"declaration":1,"left_out":["web"],"resources":[ {"id":"app","type":"container","name":"app","image":"` + pinnedImage + `","networks":["predecessor_default"]} @@ -506,7 +506,7 @@ func TestKeptNetworksAndLeftOutModulesAreReadStrictly(t *testing.T) { } // A container may ask for a capability by name, and nothing else (novox/hq ADR 0170). -func TestACapabilityIsNamedOrRefused(t *testing.T) { +func testACapabilityIsNamedOrRefused(t *testing.T) { image := "postgres@sha256:" + strings.Repeat("a", 64) d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"fw","type":"container","name":"fw","image":"` + image + `","network":"host","capabilities":["NET_ADMIN","CAP_NET_RAW"]} @@ -527,7 +527,7 @@ func TestACapabilityIsNamedOrRefused(t *testing.T) { // A container may send its output to the machine's journal, and nowhere else but the runtime's own // file (novox/hq ADR 0179): what reads the machine's logs then reads the container's too. -func TestAContainerMayLogToTheJournalAndNowhereElse(t *testing.T) { +func testAContainerMayLogToTheJournalAndNowhereElse(t *testing.T) { image := "postgres@sha256:" + strings.Repeat("a", 64) d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"front","type":"container","name":"front","image":"` + image + `","logging":"journald"} @@ -548,7 +548,7 @@ func TestAContainerMayLogToTheJournalAndNowhereElse(t *testing.T) { // Issue 1024, ADR 0422: a directory or archive says how it treats files it did not write. -func TestADirectoryAndAnArchiveSayWhatTheyDoWithUnknownFiles(t *testing.T) { +func testADirectoryAndAnArchiveSayWhatTheyDoWithUnknownFiles(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"d","type":"directory","path":"/home/a/x","unknown":"move-out","knows":["keep","sub/"]}, {"id":"r","type":"archive","source":"https://x/y","digest":"sha256:` + strings.Repeat("a", 64) + `","path":"/home/a/y","unknown":"module"} @@ -570,7 +570,7 @@ func TestADirectoryAndAnArchiveSayWhatTheyDoWithUnknownFiles(t *testing.T) { } } -func TestBadUnknownAndKnowsFormsAreRefusedNamingTheResource(t *testing.T) { +func testBadUnknownAndKnowsFormsAreRefusedNamingTheResource(t *testing.T) { digest := `"digest":"sha256:` + strings.Repeat("a", 64) + `"` cases := map[string]string{ "bad value": `{"id":"d","type":"directory","path":"/x","unknown":"delete"}`, @@ -593,3 +593,36 @@ func TestBadUnknownAndKnowsFormsAreRefusedNamingTheResource(t *testing.T) { }) } } + +// TestDeclaration holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestDeclaration(t *testing.T) { + t.Run("AValidDeclarationParsesInOrder", testAValidDeclarationParsesInOrder) + t.Run("AnUnknownTypeRefusesTheWholeDeclaration", testAnUnknownTypeRefusesTheWholeDeclaration) + t.Run("AnUnknownFieldIsRefused", testAnUnknownFieldIsRefused) + t.Run("AFieldTheTypeDoesNotUseIsRefusedNotIgnored", testAFieldTheTypeDoesNotUseIsRefusedNotIgnored) + t.Run("AnUnknownVersionIsRefusedWhole", testAnUnknownVersionIsRefusedWhole) + t.Run("EveryProblemIsReportedAtOnce", testEveryProblemIsReportedAtOnce) + t.Run("IdentityIsRequiredAndUnique", testIdentityIsRequiredAndUnique) + t.Run("ModeIsRefusedUnlessItMeansWhatItLooksLike", testModeIsRefusedUnlessItMeansWhatItLooksLike) + t.Run("ARefusalSaysNothingWasApplied", testARefusalSaysNothingWasApplied) + t.Run("AnEmptyDeclarationIsAMistake", testAnEmptyDeclarationIsAMistake) + t.Run("AnActionOverTheLinkIsRefused", testAnActionOverTheLinkIsRefused) + t.Run("AnActionWithoutVerifyIsRefused", testAnActionWithoutVerifyIsRefused) + t.Run("AnImageMustBePinnedByDigest", testAnImageMustBePinnedByDigest) + t.Run("AnImageTheMachineHoldsIsNamedByItsOwnDigest", testAnImageTheMachineHoldsIsNamedByItsOwnDigest) + t.Run("AFieldTheNewTypesDoNotUseIsRefused", testAFieldTheNewTypesDoNotUseIsRefused) + t.Run("TheVocabularyIsTheTwelveShapesTheMeshNeeds", testTheVocabularyIsTheTwelveShapesTheMeshNeeds) + t.Run("ADeclarationFromDiskMayBeAnnotated", testADeclarationFromDiskMayBeAnnotated) + t.Run("SomethingInsideAValueIsNotAComment", testSomethingInsideAValueIsNotAComment) + t.Run("SomethingAfterTheDeclarationIsRefused", testSomethingAfterTheDeclarationIsRefused) + t.Run("ANetworkNameIsRefusedIfItIsNotOne", testANetworkNameIsRefusedIfItIsNotOne) + t.Run("AFileAskingForASecretItWasNotGivenIsRefused", testAFileAskingForASecretItWasNotGivenIsRefused) + t.Run("ASecretTheContentNeverUsesIsRefused", testASecretTheContentNeverUsesIsRefused) + t.Run("AContainersResolverAndAddressAreAddressesOrRefused", testAContainersResolverAndAddressAreAddressesOrRefused) + t.Run("AnExplicitlyEmptyDeclarationIsAccepted", testAnExplicitlyEmptyDeclarationIsAccepted) + t.Run("KeptNetworksAndLeftOutModulesAreReadStrictly", testKeptNetworksAndLeftOutModulesAreReadStrictly) + t.Run("ACapabilityIsNamedOrRefused", testACapabilityIsNamedOrRefused) + t.Run("AContainerMayLogToTheJournalAndNowhereElse", testAContainerMayLogToTheJournalAndNowhereElse) + t.Run("ADirectoryAndAnArchiveSayWhatTheyDoWithUnknownFiles", testADirectoryAndAnArchiveSayWhatTheyDoWithUnknownFiles) + t.Run("BadUnknownAndKnowsFormsAreRefusedNamingTheResource", testBadUnknownAndKnowsFormsAreRefusedNamingTheResource) +} diff --git a/go/declaration/filereference_test.go b/go/declaration/filereference_test.go index be34ce1..e6fe246 100644 --- a/go/declaration/filereference_test.go +++ b/go/declaration/filereference_test.go @@ -15,7 +15,7 @@ func refFile(extra string) string { } // 1694/AC1: a reference round trips, and an invalid one is refused naming the field. -func TestAFileReferenceRoundTripsAndABadOneNamesItsField(t *testing.T) { +func testAFileReferenceRoundTripsAndABadOneNamesItsField(t *testing.T) { d, err := Parse([]byte(refFile(""))) if err != nil { t.Fatal(err) @@ -49,7 +49,7 @@ func TestAFileReferenceRoundTripsAndABadOneNamesItsField(t *testing.T) { } // 1694/AC2: a declaration as today's SDK writes it reads unchanged and its bytes carry no new key. -func TestAnInlineFileIsReadAndWrittenAsBefore(t *testing.T) { +func testAnInlineFileIsReadAndWrittenAsBefore(t *testing.T) { const in = `{"id":"m.f","type":"file","path":"/e","content":"hello"}` d, err := Parse([]byte(`{"declaration":1,"resources":[` + in + `]}`)) if err != nil { @@ -65,7 +65,7 @@ func TestAnInlineFileIsReadAndWrittenAsBefore(t *testing.T) { } // 1694/AC3: the controller's helper makes a reference of a big file and the engine's check refuses a changed byte. -func TestABigFileBecomesAReferenceAndAChangedByteFailsTheCheck(t *testing.T) { +func testABigFileBecomesAReferenceAndAChangedByteFailsTheCheck(t *testing.T) { big := strings.Repeat("x", FileReferenceThreshold+1) f := &File{ID: "m.f", Type: TypeFile, Path: "/e", Content: big} if !f.TravelsByDigest() { @@ -95,7 +95,7 @@ func TestABigFileBecomesAReferenceAndAChangedByteFailsTheCheck(t *testing.T) { } // 1694/AC4: what stays inline, and bytes files travel decoded. -func TestWhichFilesTravelByDigest(t *testing.T) { +func testWhichFilesTravelByDigest(t *testing.T) { small := strings.Repeat("x", FileReferenceThreshold) big := strings.Repeat("x", FileReferenceThreshold+1) for name, f := range map[string]File{ @@ -126,7 +126,7 @@ func TestWhichFilesTravelByDigest(t *testing.T) { } } -func TestTheSharedNames(t *testing.T) { +func testTheSharedNames(t *testing.T) { if FileBucketPrefix != "declaration-files-" || FileReferencesCapability != "file-references" || FileReferenceThreshold != 65536 { t.Fatal("a name changed") } @@ -136,7 +136,7 @@ func TestTheSharedNames(t *testing.T) { } // 1694/AC6: each node has a bucket of its own, so a grant names it and never needs a `*` (ADR 0486 §2). -func TestEachNodeHasABucketOfItsOwn(t *testing.T) { +func testEachNodeHasABucketOfItsOwn(t *testing.T) { for _, n := range []string{"ace", "g14", "novox", "shanks"} { b, err := FileBucketFor(n) if err != nil || b != "declaration-files-"+n { @@ -156,3 +156,13 @@ func TestEachNodeHasABucketOfItsOwn(t *testing.T) { } } } + +// TestFilereference holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestFilereference(t *testing.T) { + t.Run("AFileReferenceRoundTripsAndABadOneNamesItsField", testAFileReferenceRoundTripsAndABadOneNamesItsField) + t.Run("AnInlineFileIsReadAndWrittenAsBefore", testAnInlineFileIsReadAndWrittenAsBefore) + t.Run("ABigFileBecomesAReferenceAndAChangedByteFailsTheCheck", testABigFileBecomesAReferenceAndAChangedByteFailsTheCheck) + t.Run("WhichFilesTravelByDigest", testWhichFilesTravelByDigest) + t.Run("TheSharedNames", testTheSharedNames) + t.Run("EachNodeHasABucketOfItsOwn", testEachNodeHasABucketOfItsOwn) +} diff --git a/go/declaration/groups_test.go b/go/declaration/groups_test.go index 193d8f5..c648223 100644 --- a/go/declaration/groups_test.go +++ b/go/declaration/groups_test.go @@ -9,7 +9,7 @@ import ( // A container may be given supplementary groups (novox/hq ADR 0480 clause 7, issue 1636): numbers, each above zero. // 1636/AC1: a declaration with groups decodes, is written back with them, and a declaration without them has none. -func TestAContainerWithGroupsDecodesAndIsWrittenBack(t *testing.T) { +func testAContainerWithGroupsDecodesAndIsWrittenBack(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"n8n.server","type":"container","name":"n8n","image":"` + anImage + `","groups":[2000,2001]}, {"id":"n8n.plain","type":"container","name":"plain","image":"` + anImage + `"}]}`)) @@ -34,7 +34,7 @@ func TestAContainerWithGroupsDecodesAndIsWrittenBack(t *testing.T) { } // 1636/AC2: zero, a number below it, one the kernel keeps for "no group" and a number twice are refused, naming why. -func TestAContainersGroupsAreWholeNumbersAboveZeroAndNotRepeated(t *testing.T) { +func testAContainersGroupsAreWholeNumbersAboveZeroAndNotRepeated(t *testing.T) { for want, groups := range map[string]string{ "never root": `[0]`, "below zero": `[-5]`, @@ -53,3 +53,9 @@ func TestAContainersGroupsAreWholeNumbersAboveZeroAndNotRepeated(t *testing.T) { t.Error("a group named by text was accepted") } } + +// TestGroups holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestGroups(t *testing.T) { + t.Run("AContainerWithGroupsDecodesAndIsWrittenBack", testAContainerWithGroupsDecodesAndIsWrittenBack) + t.Run("AContainersGroupsAreWholeNumbersAboveZeroAndNotRepeated", testAContainersGroupsAreWholeNumbersAboveZeroAndNotRepeated) +} diff --git a/go/declaration/identity_test.go b/go/declaration/identity_test.go index e97225f..ca4351c 100644 --- a/go/declaration/identity_test.go +++ b/go/declaration/identity_test.go @@ -8,7 +8,7 @@ import ( const anImage = "docker.io/library/x@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" // The fields ADR 0325 adds, read as the controller sends them (novox/hq ADR 0325 §2, §3, §5, issue 567). -func TestIdentitiesUsersAndARunAsAreReadAsSent(t *testing.T) { +func testIdentitiesUsersAndARunAsAreReadAsSent(t *testing.T) { d, err := Parse([]byte(`{"declaration":1, "identities":[{"module":"radarr","name":"mesh-radarr","uid":61001,"gid":61001}], "resources":[ @@ -35,7 +35,7 @@ func TestIdentitiesUsersAndARunAsAreReadAsSent(t *testing.T) { // What the engine refuses on arrival: a root number, an identity not named for its module, a field it does not // know, a user that writes a read access, a variable set twice. -func TestWhatADeclarationMayNotSayOfAnIdentity(t *testing.T) { +func testWhatADeclarationMayNotSayOfAnIdentity(t *testing.T) { for want, raw := range map[string]string{ "never root": `"identities":[{"module":"m","name":"mesh-m","uid":0,"gid":61001}],"resources":[]`, "named mesh-": `"identities":[{"module":"m","name":"m","uid":61001,"gid":61001}],` + @@ -59,3 +59,9 @@ func TestWhatADeclarationMayNotSayOfAnIdentity(t *testing.T) { } } } + +// TestIdentity holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestIdentity(t *testing.T) { + t.Run("IdentitiesUsersAndARunAsAreReadAsSent", testIdentitiesUsersAndARunAsAreReadAsSent) + t.Run("WhatADeclarationMayNotSayOfAnIdentity", testWhatADeclarationMayNotSayOfAnIdentity) +} diff --git a/go/declaration/module_test.go b/go/declaration/module_test.go index cdd4b02..20047f6 100644 --- a/go/declaration/module_test.go +++ b/go/declaration/module_test.go @@ -4,7 +4,7 @@ import "testing" // 1132/AC1: a resource says its module, and the declaration says each module's build; a resource that does // not say gets today's guess, so an older controller still works. -func TestAResourceSaysItsModuleAndTheDeclarationItsBuilds(t *testing.T) { +func testAResourceSaysItsModuleAndTheDeclarationItsBuilds(t *testing.T) { d, err := Parse([]byte(`{"declaration":1, "builds":{"novox.be":"b-9","web":"b-4"}, "resources":[ @@ -30,7 +30,7 @@ func TestAResourceSaysItsModuleAndTheDeclarationItsBuilds(t *testing.T) { } // 1132/AC1: the module the declaration gives wins over the guess, even when the id would guess another. -func TestTheDeclaredModuleWinsOverTheGuess(t *testing.T) { +func testTheDeclaredModuleWinsOverTheGuess(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"a.b.c","type":"directory","path":"/srv/a","module":"a.b"}, {"id":"x.y","type":"directory","path":"/srv/x","module":"other"}]}`)) @@ -43,7 +43,7 @@ func TestTheDeclaredModuleWinsOverTheGuess(t *testing.T) { } // 1132/AC1: every kind accepts the field. -func TestEveryKindAcceptsAModule(t *testing.T) { +func testEveryKindAcceptsAModule(t *testing.T) { for _, typ := range []Type{TypeDirectory, TypeFile, TypeUser, TypeNetwork, TypeAccess, TypeArchive, TypeProcess, TypeService, TypeOpening, TypePackage, TypeContainer, TypeAction} { r := newOf(typ) @@ -58,3 +58,10 @@ func TestEveryKindAcceptsAModule(t *testing.T) { } } } + +// TestModule holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestModule(t *testing.T) { + t.Run("AResourceSaysItsModuleAndTheDeclarationItsBuilds", testAResourceSaysItsModuleAndTheDeclarationItsBuilds) + t.Run("TheDeclaredModuleWinsOverTheGuess", testTheDeclaredModuleWinsOverTheGuess) + t.Run("EveryKindAcceptsAModule", testEveryKindAcceptsAModule) +} diff --git a/go/declaration/order_test.go b/go/declaration/order_test.go index 4d2c93d..56cf9e4 100644 --- a/go/declaration/order_test.go +++ b/go/declaration/order_test.go @@ -12,7 +12,7 @@ func withOrder(order string) string { return `{"declaration":1,` + order + `"resources":[{"id":"etc","type":"directory","path":"/etc/mesh","mode":"0755"}]}` } -func TestADeclarationCarriesItsEpochAndSequence(t *testing.T) { +func testADeclarationCarriesItsEpochAndSequence(t *testing.T) { d, err := Parse([]byte(withOrder(`"epoch":57,"sequence":3,`))) if err != nil { t.Fatalf("a declaration carrying its order was refused: %v", err) @@ -27,7 +27,7 @@ func TestADeclarationCarriesItsEpochAndSequence(t *testing.T) { } } -func TestAnOrderBelowZeroIsRefused(t *testing.T) { +func testAnOrderBelowZeroIsRefused(t *testing.T) { for _, order := range []string{`"epoch":-1,`, `"sequence":-4,`} { refusal := refusalFor(t, withOrder(order)) if !strings.Contains(refusal.Error(), "below zero") { @@ -38,7 +38,7 @@ func TestAnOrderBelowZeroIsRefused(t *testing.T) { // **The assignment generation it was composed from, and whether it is a put-back** (novox/hq issue 234): // absent is no claim; below zero is none the mesh assigns; a carried bundle puts nothing back. -func TestADeclarationCarriesItsGenerationAndWhetherItIsAPutBack(t *testing.T) { +func testADeclarationCarriesItsGenerationAndWhetherItIsAPutBack(t *testing.T) { d, err := Parse([]byte(withOrder(`"sequence":3,"generation":41,"put_back":true,`))) if err != nil { t.Fatalf("a declaration carrying its generation was refused: %v", err) @@ -57,3 +57,10 @@ func TestADeclarationCarriesItsGenerationAndWhetherItIsAPutBack(t *testing.T) { t.Errorf("a carried bundle said it was a put-back and was not refused for it: %v", err) } } + +// TestOrder holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestOrder(t *testing.T) { + t.Run("ADeclarationCarriesItsEpochAndSequence", testADeclarationCarriesItsEpochAndSequence) + t.Run("AnOrderBelowZeroIsRefused", testAnOrderBelowZeroIsRefused) + t.Run("ADeclarationCarriesItsGenerationAndWhetherItIsAPutBack", testADeclarationCarriesItsGenerationAndWhetherItIsAPutBack) +} diff --git a/go/declaration/ownership_test.go b/go/declaration/ownership_test.go index 76b0674..c8ad64c 100644 --- a/go/declaration/ownership_test.go +++ b/go/declaration/ownership_test.go @@ -9,7 +9,7 @@ import ( // — owned, seed or managed — and the declaration names the files the provisioner fills. The words are // read and judged here before any controller sends them. -func TestAFileSaysWhoOwnsItAtRuntime(t *testing.T) { +func testAFileSaysWhoOwnsItAtRuntime(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"o","type":"file","path":"/etc/o","content":"a","owner-at-runtime":"owned"}, {"id":"s","type":"file","path":"/etc/s","content":"a","owner-at-runtime":"seed"}, @@ -31,7 +31,7 @@ func TestAFileSaysWhoOwnsItAtRuntime(t *testing.T) { } // A file that does not say is owned by its old form, so the word means exactly what that form does. -func TestAFileThatDoesNotSayIsOwnedByItsOldForm(t *testing.T) { +func testAFileThatDoesNotSayIsOwnedByItsOldForm(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"o","type":"file","path":"/etc/o","content":"a"}, {"id":"s","type":"file","path":"/etc/s","content":"a","create-once":true}, @@ -53,7 +53,7 @@ func TestAFileThatDoesNotSayIsOwnedByItsOldForm(t *testing.T) { } } -func TestAnOwnershipThatContradictsTheFileIsRefused(t *testing.T) { +func testAnOwnershipThatContradictsTheFileIsRefused(t *testing.T) { for name, c := range map[string]struct{ resource, refusal string }{ "another word": {`{"id":"f","type":"file","path":"/etc/x","content":"a","owner-at-runtime":"mine"}`, `owner-at-runtime "mine"`}, @@ -78,7 +78,7 @@ func TestAnOwnershipThatContradictsTheFileIsRefused(t *testing.T) { } } -func TestTheDeclarationNamesTheFilesTheProvisionerFills(t *testing.T) { +func testTheDeclarationNamesTheFilesTheProvisionerFills(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"filled_files":["/etc/a","/etc/b"],"resources":[ {"id":"a","type":"file","path":"/etc/a","content":"a","owner-at-runtime":"owned"}, {"id":"b","type":"file","path":"/etc/b","into":"block","content":"b\n","owner-at-runtime":"managed"}, @@ -104,7 +104,7 @@ func TestTheDeclarationNamesTheFilesTheProvisionerFills(t *testing.T) { } } -func TestAFilledFileMustBeADeclaredFileThatSaysItsOwner(t *testing.T) { +func testAFilledFileMustBeADeclaredFileThatSaysItsOwner(t *testing.T) { for name, c := range map[string]struct{ filled, resources, refusal string }{ "not declared": {`["/etc/none"]`, `{"id":"a","type":"file","path":"/etc/a","content":"a","owner-at-runtime":"owned"}`, @@ -128,3 +128,12 @@ func TestAFilledFileMustBeADeclaredFileThatSaysItsOwner(t *testing.T) { } } } + +// TestOwnership holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestOwnership(t *testing.T) { + t.Run("AFileSaysWhoOwnsItAtRuntime", testAFileSaysWhoOwnsItAtRuntime) + t.Run("AFileThatDoesNotSayIsOwnedByItsOldForm", testAFileThatDoesNotSayIsOwnedByItsOldForm) + t.Run("AnOwnershipThatContradictsTheFileIsRefused", testAnOwnershipThatContradictsTheFileIsRefused) + t.Run("TheDeclarationNamesTheFilesTheProvisionerFills", testTheDeclarationNamesTheFilesTheProvisionerFills) + t.Run("AFilledFileMustBeADeclaredFileThatSaysItsOwner", testAFilledFileMustBeADeclaredFileThatSaysItsOwner) +} diff --git a/go/declaration/process_test.go b/go/declaration/process_test.go index 02eaff1..753771c 100644 --- a/go/declaration/process_test.go +++ b/go/declaration/process_test.go @@ -15,7 +15,7 @@ func aProcess() *Process { } // A process is part of the vocabulary, or a declaration carrying one is refused whole. -func TestAProcessIsSomethingTheHostSpeaks(t *testing.T) { +func testAProcessIsSomethingTheHostSpeaks(t *testing.T) { var found bool for _, kind := range Vocabulary() { if kind == TypeProcess { @@ -33,7 +33,7 @@ func TestAProcessIsSomethingTheHostSpeaks(t *testing.T) { // **Pinned by digest, like everything else that crosses a network.** A bundle fetched by a // reference somebody can repoint is not pinned, and it is the one thing on a machine that would // then be running code nobody reviewed. -func TestAProcesssBundleMustBePinned(t *testing.T) { +func testAProcesssBundleMustBePinned(t *testing.T) { for _, bad := range []string{"", "latest", "sha256:short", strings.Repeat("a", 64)} { d := aProcess() d.Digest = bad @@ -45,7 +45,7 @@ func TestAProcesssBundleMustBePinned(t *testing.T) { // What to run is named, never inferred. Guessing an entrypoint from which files are present makes // a process change what it runs when somebody adds a file. -func TestAProcessMustSayWhatToRun(t *testing.T) { +func testAProcessMustSayWhatToRun(t *testing.T) { d := aProcess() d.Run = nil if problems := d.validate("a process", false); len(problems) == 0 { @@ -54,7 +54,7 @@ func TestAProcessMustSayWhatToRun(t *testing.T) { } // Its name becomes a unit name and a path, so a separator in it would write somewhere nobody meant. -func TestAProcesssNameCannotEscapeItsUnit(t *testing.T) { +func testAProcesssNameCannotEscapeItsUnit(t *testing.T) { // "." and ".." are one path element each, and the mesh's own bundle directory and its parent: // removing a process named ".." would delete every bundle the mesh has, and more (novox/hq ADR // 0118). A leading dash is an option to the service manager, not a unit. @@ -68,7 +68,7 @@ func TestAProcesssNameCannotEscapeItsUnit(t *testing.T) { } // And a well-formed one is accepted, or the tests above prove only that everything is refused. -func TestAWellFormedDaemonIsAccepted(t *testing.T) { +func testAWellFormedDaemonIsAccepted(t *testing.T) { if problems := aProcess().validate("a process", false); len(problems) != 0 { t.Fatalf("a well-formed daemon was refused: %v", problems) } @@ -78,7 +78,7 @@ func TestAWellFormedDaemonIsAccepted(t *testing.T) { // once does not run on a schedule; something not running between fires cannot be restarted when a // file changes. A container's modes carry the same rule, and this is the same rule because it is // the same thing hosted differently. -func TestTheModesAreExclusive(t *testing.T) { +func testTheModesAreExclusive(t *testing.T) { both := aProcess() both.RunOnce = true both.Schedule = "0 3 * * *" @@ -110,7 +110,7 @@ func TestTheModesAreExclusive(t *testing.T) { // A cadence that is not a cadence is refused near its author, rather than by a machine at the far // end of a declaration. -func TestAScheduleMustBeACadence(t *testing.T) { +func testAScheduleMustBeACadence(t *testing.T) { for _, bad := range []string{"often", "0 3 * *", "99 3 * * *"} { p := aProcess() p.Schedule = bad @@ -121,7 +121,7 @@ func TestAScheduleMustBeACadence(t *testing.T) { } // And each mode on its own is accepted, or the tests above prove only that everything is refused. -func TestEachModeOnItsOwnIsAccepted(t *testing.T) { +func testEachModeOnItsOwnIsAccepted(t *testing.T) { once := aProcess() once.RunOnce = true if problems := once.validate("a process", false); len(problems) != 0 { @@ -148,7 +148,7 @@ func TestEachModeOnItsOwnIsAccepted(t *testing.T) { // // So it is refused here, near whoever wrote it, rather than rendered into a unit at the far end of // a declaration. -func TestAnEnvironmentValueCannotCarryALineBreak(t *testing.T) { +func testAnEnvironmentValueCannotCarryALineBreak(t *testing.T) { for _, bad := range []string{ "safe\nExecStart=/usr/bin/whatever", "carriage\rreturn", @@ -173,10 +173,24 @@ func TestAnEnvironmentValueCannotCarryALineBreak(t *testing.T) { // And ordinary awkward values are accepted, because escaping is what handles those — refusing them // too would make a module unable to hold a generated password. -func TestOrdinaryAwkwardValuesAreAccepted(t *testing.T) { +func testOrdinaryAwkwardValuesAreAccepted(t *testing.T) { p := aProcess() p.Env = map[string]string{"PASSWORD": `a%H b"c\d`} if problems := p.validate("a process", false); len(problems) != 0 { t.Fatalf("a password containing the characters passwords contain was refused: %v", problems) } } + +// TestProcess holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestProcess(t *testing.T) { + t.Run("AProcessIsSomethingTheHostSpeaks", testAProcessIsSomethingTheHostSpeaks) + t.Run("AProcesssBundleMustBePinned", testAProcesssBundleMustBePinned) + t.Run("AProcessMustSayWhatToRun", testAProcessMustSayWhatToRun) + t.Run("AProcesssNameCannotEscapeItsUnit", testAProcesssNameCannotEscapeItsUnit) + t.Run("AWellFormedDaemonIsAccepted", testAWellFormedDaemonIsAccepted) + t.Run("TheModesAreExclusive", testTheModesAreExclusive) + t.Run("AScheduleMustBeACadence", testAScheduleMustBeACadence) + t.Run("EachModeOnItsOwnIsAccepted", testEachModeOnItsOwnIsAccepted) + t.Run("AnEnvironmentValueCannotCarryALineBreak", testAnEnvironmentValueCannotCarryALineBreak) + t.Run("OrdinaryAwkwardValuesAreAccepted", testOrdinaryAwkwardValuesAreAccepted) +} diff --git a/go/declaration/provisioner_test.go b/go/declaration/provisioner_test.go index 233aa40..91dc265 100644 --- a/go/declaration/provisioner_test.go +++ b/go/declaration/provisioner_test.go @@ -8,7 +8,7 @@ import ( // Defends novox/hq ADR 0349 §4 (issue 668): the declaration names the account the provisioner's client runs as, // the only one whose write of a filled file the node-engine accepts. Read here before any controller sends it. -func TestTheDeclarationNamesTheProvisionersAccount(t *testing.T) { +func testTheDeclarationNamesTheProvisionersAccount(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"provisioner_account":"mesh-node-provisioner","filled_files":["/etc/a"], "resources":[{"id":"a","type":"file","path":"/etc/a","content":"a","owner-at-runtime":"owned"}]}`)) if err != nil { @@ -26,7 +26,7 @@ func TestTheDeclarationNamesTheProvisionersAccount(t *testing.T) { } } -func TestAProvisionersAccountThatIsNoAccountIsRefused(t *testing.T) { +func testAProvisionersAccountThatIsNoAccountIsRefused(t *testing.T) { for name, account := range map[string]string{"root": "root", "not a name": "a b", "a path": "../x"} { _, err := Parse([]byte(`{"declaration":1,"provisioner_account":"` + account + `", "resources":[{"id":"a","type":"file","path":"/etc/a","content":"a"}]}`)) @@ -35,3 +35,9 @@ func TestAProvisionersAccountThatIsNoAccountIsRefused(t *testing.T) { } } } + +// TestProvisioner holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestProvisioner(t *testing.T) { + t.Run("TheDeclarationNamesTheProvisionersAccount", testTheDeclarationNamesTheProvisionersAccount) + t.Run("AProvisionersAccountThatIsNoAccountIsRefused", testAProvisionersAccountThatIsNoAccountIsRefused) +} diff --git a/go/declaration/replaces_test.go b/go/declaration/replaces_test.go index c470505..017ea11 100644 --- a/go/declaration/replaces_test.go +++ b/go/declaration/replaces_test.go @@ -15,7 +15,7 @@ func replacing(extra string) string { return `{"declaration":1,"resources":[` + strings.Replace(aReplacingProcess, "%s", extra, 1) + `]}` } -func TestAProcessMayNameWhatItReplaces(t *testing.T) { +func testAProcessMayNameWhatItReplaces(t *testing.T) { d, err := Parse([]byte(replacing(`,"replaces":["m.server"]`))) if err != nil { t.Fatalf("a process replacing an undeclared container was refused: %v", err) @@ -27,7 +27,7 @@ func TestAProcessMayNameWhatItReplaces(t *testing.T) { // What is replaced is what the declaration no longer says. A resource still declared is applied, // and handing it over as well would remove something the same declaration asks to keep. -func TestAProcessMayNotReplaceSomethingStillDeclared(t *testing.T) { +func testAProcessMayNotReplaceSomethingStillDeclared(t *testing.T) { raw := `{"declaration":1,"resources":[ {"id":"m.server","type":"directory","path":"/tmp/x"}, ` + strings.Replace(aReplacingProcess, "%s", `,"replaces":["m.server"]`, 1) + `]}` @@ -36,7 +36,7 @@ func TestAProcessMayNotReplaceSomethingStillDeclared(t *testing.T) { } } -func TestOnlyAProcessThatStaysUpReplacesAnything(t *testing.T) { +func testOnlyAProcessThatStaysUpReplacesAnything(t *testing.T) { for _, mode := range []string{`,"run-once":true`, `,"schedule":"0 3 * * *"`} { if _, err := Parse([]byte(replacing(mode + `,"replaces":["m.server"]`))); err == nil { t.Errorf("a process with %s was allowed to replace something", mode) @@ -44,7 +44,7 @@ func TestOnlyAProcessThatStaysUpReplacesAnything(t *testing.T) { } } -func TestAProcessCannotReplaceItselfOrNothing(t *testing.T) { +func testAProcessCannotReplaceItselfOrNothing(t *testing.T) { for _, bad := range []string{`,"replaces":["m.controller"]`, `,"replaces":[""]`} { if _, err := Parse([]byte(replacing(bad))); err == nil { t.Errorf("replaces %s was accepted", bad) @@ -52,7 +52,7 @@ func TestAProcessCannotReplaceItselfOrNothing(t *testing.T) { } } -func TestOneThingHasOneReplacement(t *testing.T) { +func testOneThingHasOneReplacement(t *testing.T) { second := strings.NewReplacer(`"id":"m.controller"`, `"id":"m.other"`, `"name":"m"`, `"name":"other"`). Replace(strings.Replace(aReplacingProcess, "%s", `,"replaces":["m.server"]`, 1)) raw := `{"declaration":1,"resources":[` + @@ -61,3 +61,12 @@ func TestOneThingHasOneReplacement(t *testing.T) { t.Fatal("two processes replacing one resource were accepted") } } + +// TestReplaces holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestReplaces(t *testing.T) { + t.Run("AProcessMayNameWhatItReplaces", testAProcessMayNameWhatItReplaces) + t.Run("AProcessMayNotReplaceSomethingStillDeclared", testAProcessMayNotReplaceSomethingStillDeclared) + t.Run("OnlyAProcessThatStaysUpReplacesAnything", testOnlyAProcessThatStaysUpReplacesAnything) + t.Run("AProcessCannotReplaceItselfOrNothing", testAProcessCannotReplaceItselfOrNothing) + t.Run("OneThingHasOneReplacement", testOneThingHasOneReplacement) +} diff --git a/go/declaration/schedule_declaration_test.go b/go/declaration/schedule_declaration_test.go index d87d1fb..5996c8f 100644 --- a/go/declaration/schedule_declaration_test.go +++ b/go/declaration/schedule_declaration_test.go @@ -11,7 +11,7 @@ import ( func schedulePinned() string { return "registry.example/runtime@sha256:" + strings.Repeat("a", 64) } -func TestAScheduledContainerCarriesTheCronField(t *testing.T) { +func testAScheduledContainerCarriesTheCronField(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"sync","type":"container","name":"sync","image":"` + schedulePinned() + `","schedule":"0 3 * * *"} ]}`)) @@ -27,7 +27,7 @@ func TestAScheduledContainerCarriesTheCronField(t *testing.T) { } } -func TestAMalformedScheduleIsRefused(t *testing.T) { +func testAMalformedScheduleIsRefused(t *testing.T) { _, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"sync","type":"container","name":"sync","image":"` + schedulePinned() + `","schedule":"every night"} ]}`)) @@ -39,7 +39,7 @@ func TestAMalformedScheduleIsRefused(t *testing.T) { } } -func TestAContainerCannotBeBothRunOnceAndScheduled(t *testing.T) { +func testAContainerCannotBeBothRunOnceAndScheduled(t *testing.T) { // A container runs once and gates, or on a cadence, or stays up — never two (novox/hq ADR 0053). _, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"sync","type":"container","name":"sync","image":"` + schedulePinned() + `","run-once":true,"schedule":"0 3 * * *"} @@ -51,3 +51,10 @@ func TestAContainerCannotBeBothRunOnceAndScheduled(t *testing.T) { t.Errorf("refused for the wrong reason: %v", err) } } + +// TestScheduleDeclaration holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestScheduleDeclaration(t *testing.T) { + t.Run("AScheduledContainerCarriesTheCronField", testAScheduledContainerCarriesTheCronField) + t.Run("AMalformedScheduleIsRefused", testAMalformedScheduleIsRefused) + t.Run("AContainerCannotBeBothRunOnceAndScheduled", testAContainerCannotBeBothRunOnceAndScheduled) +} diff --git a/go/declaration/userscope_test.go b/go/declaration/userscope_test.go index fdce635..25e46b1 100644 --- a/go/declaration/userscope_test.go +++ b/go/declaration/userscope_test.go @@ -7,7 +7,7 @@ import ( // novox/hq ADR 0177: a unit is in the system manager or an account's own; a user-scoped one names // the account, a system one may not, and any other word is refused. -func TestAUserScopedUnitNamesItsAccountAndASystemOneMayNot(t *testing.T) { +func testAUserScopedUnitNamesItsAccountAndASystemOneMayNot(t *testing.T) { cases := []struct{ scope, user, wants string }{ {"user", "ops", ""}, {"", "", ""}, @@ -30,7 +30,7 @@ func TestAUserScopedUnitNamesItsAccountAndASystemOneMayNot(t *testing.T) { } // novox/hq ADR 0177: lingering is a field of the account, absent meaning nothing asserted. -func TestAnAccountMayBeDeclaredToLinger(t *testing.T) { +func testAnAccountMayBeDeclaredToLinger(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[{"id":"m.login","type":"user","name":"ops","linger":true}]}`)) if err != nil { t.Fatal(err) @@ -47,3 +47,9 @@ func TestAnAccountMayBeDeclaredToLinger(t *testing.T) { t.Fatal("an account that said nothing about lingering asserts it") } } + +// TestUserscope holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestUserscope(t *testing.T) { + t.Run("AUserScopedUnitNamesItsAccountAndASystemOneMayNot", testAUserScopedUnitNamesItsAccountAndASystemOneMayNot) + t.Run("AnAccountMayBeDeclaredToLinger", testAnAccountMayBeDeclaredToLinger) +} diff --git a/go/provider/harness/grants_moved_test.go b/go/provider/harness/grants_moved_test.go index 920bb7a..cf43517 100644 --- a/go/provider/harness/grants_moved_test.go +++ b/go/provider/harness/grants_moved_test.go @@ -46,7 +46,7 @@ func (w *movedWorld) settled() { // The logins the grant file gives are asked for: made as the grant path makes them, never offered for retirement, // beside a moved consumer the helper makes from its plan. -func TestAConsumerOnlyTheGrantFileGivesIsAskedForAndMade(t *testing.T) { +func testAConsumerOnlyTheGrantFileGivesIsAskedForAndMade(t *testing.T) { bound, logins := bindings(1) w := newMovedWorld(t, bound) w.a.inv.Active = []string{"mesh_ace_baserow", "mesh_ace_letta", logins[0]} @@ -78,7 +78,7 @@ func TestAConsumerOnlyTheGrantFileGivesIsAskedForAndMade(t *testing.T) { } // A login both a plan and the grant file name is the plan's: made once, with the plan's credential. -func TestALoginAPlanNamesIsNotMadeFromTheGrantFile(t *testing.T) { +func testALoginAPlanNamesIsNotMadeFromTheGrantFile(t *testing.T) { bound, logins := bindings(1) w := newMovedWorld(t, bound) w.bus.bind("ace", "app1", nil) @@ -93,7 +93,7 @@ func TestALoginAPlanNamesIsNotMadeFromTheGrantFile(t *testing.T) { } // An unreadable grant file is not nobody asking (issue 241): nothing is retired while it cannot be read. -func TestAnUnreadableGrantFileRetiresNothing(t *testing.T) { +func testAnUnreadableGrantFileRetiresNothing(t *testing.T) { bound, logins := bindings(1) w := newMovedWorld(t, bound) w.a.inv.Active = []string{"mesh_ace_baserow", logins[0]} @@ -107,7 +107,7 @@ func TestAnUnreadableGrantFileRetiresNothing(t *testing.T) { // A login the grant file stops giving is not retired by the provider itself (novox/hq ADR 0570 clause 5): every // retirement waits for the operator's yes on a provision's key. -func TestALoginTheGrantFileStopsGivingIsRetired(t *testing.T) { +func testALoginTheGrantFileStopsGivingIsRetired(t *testing.T) { bound, _ := bindings(1) w := newMovedWorld(t, bound) w.bus.bind("ace", "app1", nil) @@ -122,3 +122,11 @@ func TestALoginTheGrantFileStopsGivingIsRetired(t *testing.T) { t.Fatalf("retired %v, deleted %v, by itself", w.a.removed, w.a.deleted) } } + +// TestGrantsMoved holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestGrantsMoved(t *testing.T) { + t.Run("AConsumerOnlyTheGrantFileGivesIsAskedForAndMade", testAConsumerOnlyTheGrantFileGivesIsAskedForAndMade) + t.Run("ALoginAPlanNamesIsNotMadeFromTheGrantFile", testALoginAPlanNamesIsNotMadeFromTheGrantFile) + t.Run("AnUnreadableGrantFileRetiresNothing", testAnUnreadableGrantFileRetiresNothing) + t.Run("ALoginTheGrantFileStopsGivingIsRetired", testALoginTheGrantFileStopsGivingIsRetired) +} diff --git a/go/provider/harness/harness_test.go b/go/provider/harness/harness_test.go index d90c7b4..2e84e6d 100644 --- a/go/provider/harness/harness_test.go +++ b/go/provider/harness/harness_test.go @@ -45,7 +45,7 @@ func (w *world) give(given ...map[string]any) { } } -func TestAConsumerIsCreatedOnceUnderTheMeshsLoginAndPassword(t *testing.T) { +func testAConsumerIsCreatedOnceUnderTheMeshsLoginAndPassword(t *testing.T) { w := newWorld(t) w.give(map[string]any{"as": "mesh_ace_letta", "node": "ace", "values": map[string]any{"name": "letta"}}) w.h.Reconcile(ctx) @@ -59,7 +59,7 @@ func TestAConsumerIsCreatedOnceUnderTheMeshsLoginAndPassword(t *testing.T) { } } -func TestAConsumerNoLongerAskedForIsRetiredOnceStable(t *testing.T) { +func testAConsumerNoLongerAskedForIsRetiredOnceStable(t *testing.T) { w := newWorld(t) w.give(map[string]any{"as": "a"}, map[string]any{"as": "b"}) w.h.Reconcile(ctx) @@ -76,7 +76,7 @@ func TestAConsumerNoLongerAskedForIsRetiredOnceStable(t *testing.T) { } } -func TestNothingReadIsNotNobodyAsking(t *testing.T) { +func testNothingReadIsNotNobodyAsking(t *testing.T) { for name, content := range map[string]string{ "unreadable": "", "not JSON": "{", @@ -100,7 +100,7 @@ func TestNothingReadIsNotNobodyAsking(t *testing.T) { } } -func TestALostConsumerIsAppliedAgainAndBraked(t *testing.T) { +func testALostConsumerIsAppliedAgainAndBraked(t *testing.T) { w := newWorld(t) w.give(map[string]any{"as": "a"}) w.h.Reconcile(ctx) @@ -120,7 +120,7 @@ func TestALostConsumerIsAppliedAgainAndBraked(t *testing.T) { } } -func TestAFailingCreateIsRetriedAndSaidOnce(t *testing.T) { +func testAFailingCreateIsRetriedAndSaidOnce(t *testing.T) { w := newWorld(t) w.a.failing = &pgErr{"extension \"nope\" refused, password pw-a"} w.give(map[string]any{"as": "a"}) @@ -147,8 +147,18 @@ type pgErr struct{ s string } func (e *pgErr) Error() string { return e.s } -func TestScrubRemovesThePassword(t *testing.T) { +func testScrubRemovesThePassword(t *testing.T) { if got := scrub(&pgErr{"bad pw a/b c and a%2Fb+c"}, "a/b c"); strings.Contains(got, "a/b c") || strings.Contains(got, "a%2Fb+c") { t.Fatal(got) } } + +// TestHarness holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestHarness(t *testing.T) { + t.Run("AConsumerIsCreatedOnceUnderTheMeshsLoginAndPassword", testAConsumerIsCreatedOnceUnderTheMeshsLoginAndPassword) + t.Run("AConsumerNoLongerAskedForIsRetiredOnceStable", testAConsumerNoLongerAskedForIsRetiredOnceStable) + t.Run("NothingReadIsNotNobodyAsking", testNothingReadIsNotNobodyAsking) + t.Run("ALostConsumerIsAppliedAgainAndBraked", testALostConsumerIsAppliedAgainAndBraked) + t.Run("AFailingCreateIsRetriedAndSaidOnce", testAFailingCreateIsRetriedAndSaidOnce) + t.Run("ScrubRemovesThePassword", testScrubRemovesThePassword) +} diff --git a/go/provider/harness/moved_test.go b/go/provider/harness/moved_test.go index bce61ff..92ae2b1 100644 --- a/go/provider/harness/moved_test.go +++ b/go/provider/harness/moved_test.go @@ -188,7 +188,7 @@ func bindings(n int) (map[[2]string]string, []string) { } // The consumers' file makes the same calls on the adapter as the equivalent grant files do (the harness). -func TestTheConsumersFileMakesWhatTheGrantFilesMake(t *testing.T) { +func testTheConsumersFileMakesWhatTheGrantFilesMake(t *testing.T) { letta := provider.ConsumerIdentity("ace", "letta") plain := provider.ConsumerIdentity("ace", "plain") @@ -242,7 +242,7 @@ func TestTheConsumersFileMakesWhatTheGrantFilesMake(t *testing.T) { // A consumer whose plan goes retires nothing by itself, however long (novox/hq ADR 0570 clause 5): its login is retired, // never dropped, and its entry removed only once its provision is retiring with the operator's yes. -func TestAConsumerMissingFromOneReadRetiresNothing(t *testing.T) { +func testAConsumerMissingFromOneReadRetiresNothing(t *testing.T) { bound, logins := bindings(2) w := newMovedWorld(t, bound) w.bus.bind("ace", "app1", nil) @@ -271,7 +271,7 @@ func TestAConsumerMissingFromOneReadRetiresNothing(t *testing.T) { } // A plan list that was never read is not nobody asking. -func TestNoPlansReadRetiresNothing(t *testing.T) { +func testNoPlansReadRetiresNothing(t *testing.T) { bound, _ := bindings(1) w := newMovedWorld(t, bound) w.bus.bind("ace", "app1", nil) @@ -288,7 +288,7 @@ func TestNoPlansReadRetiresNothing(t *testing.T) { } // A consumer whose credential is not in the file yet is waited for: nothing is made for it. -func TestAConsumerWithoutACredentialIsWaitedFor(t *testing.T) { +func testAConsumerWithoutACredentialIsWaitedFor(t *testing.T) { w := newMovedWorld(t, map[[2]string]string{}) w.bus.bind("ace", "letta", map[string]any{"name": "letta"}) outcomes := w.pass() @@ -301,7 +301,7 @@ func TestAConsumerWithoutACredentialIsWaitedFor(t *testing.T) { // The grant path restarts itself when the consumers file is first filled. // A consumer whose module has a slug keeps the login its plan names, the slug first (issue 788): the adapter makes it // under that login, and the retirement counts it as asked for, so a pass retires nothing. -func TestASluggedConsumerKeepsTheLoginItsPlanNames(t *testing.T) { +func testASluggedConsumerKeepsTheLoginItsPlanNames(t *testing.T) { agent := provider.ConsumerIdentity("ace", "agent") w := newMovedWorld(t, map[[2]string]string{{"ace", "claude-code"}: "pw-agent"}) w.bus.putPlan("ace", "claude-code", map[string]any{"provision": ProvisionName, "provider": "novox", @@ -328,7 +328,7 @@ func TestASluggedConsumerKeepsTheLoginItsPlanNames(t *testing.T) { // A provider whose spec names the plan's `as` as the login and no grant file (the keycloak shape) makes the consumer // under that name with the values its spec composes, and a grant file that cannot be read does not hold up its // retirement pass. -func TestASpecWithoutGrantsMakesUnderThePlansAsAndIgnoresTheGrantFile(t *testing.T) { +func testASpecWithoutGrantsMakesUnderThePlansAsAndIgnoresTheGrantFile(t *testing.T) { a := &recorder{} bus := &bucketBus{buckets: map[string]map[string][]byte{}} now := time.Date(2026, 10, 15, 12, 0, 0, 0, time.UTC) @@ -391,7 +391,7 @@ var keycloakSpec = MovedSpec{Module: "keycloak", Provision: ProvisionName, // The consumers' file makes the same call on the adapter as the equivalent grant file does (the harness), for a // provider of the keycloak shape: the same client id, secret and values; and the entry tells the consumer what the // provider serves. -func TestTheConsumersFileMakesWhatTheGrantFileMakesForTheKeycloakShape(t *testing.T) { +func testTheConsumersFileMakesWhatTheGrantFileMakesForTheKeycloakShape(t *testing.T) { grafana := provider.ConsumerIdentity("home", "grafana") values := map[string]any{"name": grafanaNames["name"], "internal-name": grafanaNames["internal-name"]} for k, v := range grafanaContributes { @@ -439,7 +439,7 @@ func TestTheConsumersFileMakesWhatTheGrantFileMakesForTheKeycloakShape(t *testin } // A consumer whose plan names its client id (a slugged module) keeps it, and the retirement counts it as asked for. -func TestAConsumerNamedByItsPlanKeepsItsClientId(t *testing.T) { +func testAConsumerNamedByItsPlanKeepsItsClientId(t *testing.T) { agent := provider.ConsumerIdentity("home", "dashboard") w := newMovedWorldOf(t, map[[2]string]string{{"home", "grafana-2"}: "pw"}, keycloakSpec) w.bus.putPlan("home", "grafana-2", map[string]any{"provision": ProvisionName, "provider": "novox", "provider-module": "keycloak", @@ -462,3 +462,15 @@ func TestAConsumerNamedByItsPlanKeepsItsClientId(t *testing.T) { t.Fatalf("retired %v: the plan's client id was counted unasked", w.a.removed) } } + +// TestMoved holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestMoved(t *testing.T) { + t.Run("TheConsumersFileMakesWhatTheGrantFilesMake", testTheConsumersFileMakesWhatTheGrantFilesMake) + t.Run("AConsumerMissingFromOneReadRetiresNothing", testAConsumerMissingFromOneReadRetiresNothing) + t.Run("NoPlansReadRetiresNothing", testNoPlansReadRetiresNothing) + t.Run("AConsumerWithoutACredentialIsWaitedFor", testAConsumerWithoutACredentialIsWaitedFor) + t.Run("ASluggedConsumerKeepsTheLoginItsPlanNames", testASluggedConsumerKeepsTheLoginItsPlanNames) + t.Run("ASpecWithoutGrantsMakesUnderThePlansAsAndIgnoresTheGrantFile", testASpecWithoutGrantsMakesUnderThePlansAsAndIgnoresTheGrantFile) + t.Run("TheConsumersFileMakesWhatTheGrantFileMakesForTheKeycloakShape", testTheConsumersFileMakesWhatTheGrantFileMakesForTheKeycloakShape) + t.Run("AConsumerNamedByItsPlanKeepsItsClientId", testAConsumerNamedByItsPlanKeepsItsClientId) +} diff --git a/go/provider/harness/retirement_test.go b/go/provider/harness/retirement_test.go index 5b38920..4fd29e2 100644 --- a/go/provider/harness/retirement_test.go +++ b/go/provider/harness/retirement_test.go @@ -148,7 +148,7 @@ func namesOf(body map[string]any) string { // settle passes every five seconds until the same answer has held for StableFor, and one pass more. func (w *world) settle() { w.passes(StableFor + 5*time.Second) } -func TestATransientEmptyListForFourPassesRetiresNothing(t *testing.T) { +func testATransientEmptyListForFourPassesRetiresNothing(t *testing.T) { w, said := standingWorld(t) given := holding(w, 1) w.give() @@ -164,7 +164,7 @@ func TestATransientEmptyListForFourPassesRetiresNothing(t *testing.T) { // Five identical passes are twenty-five seconds — shorter than a controller restart. Both must hold: // five passes AND ten minutes of the same answer (novox/hq ADR 0230). -func TestFivePassesInTwentyFiveSecondsRetireNothingTenMinutesDo(t *testing.T) { +func testFivePassesInTwentyFiveSecondsRetireNothingTenMinutesDo(t *testing.T) { w, said := standingWorld(t) holding(w, 1) w.give() @@ -198,7 +198,7 @@ func TestFivePassesInTwentyFiveSecondsRetireNothingTenMinutesDo(t *testing.T) { } } -func TestAStableSetIsRetiredAndAskedAgainReEnables(t *testing.T) { +func testAStableSetIsRetiredAndAskedAgainReEnables(t *testing.T) { w, said := standingWorld(t) given := holding(w, 3) w.give(given[:2]...) @@ -229,7 +229,7 @@ func TestAStableSetIsRetiredAndAskedAgainReEnables(t *testing.T) { } } -func TestAnUnreadablePassStartsTheCountAgain(t *testing.T) { +func testAnUnreadablePassStartsTheCountAgain(t *testing.T) { w := newWorld(t) holding(w, 1) w.give() @@ -247,7 +247,7 @@ func TestAnUnreadablePassStartsTheCountAgain(t *testing.T) { } } -func TestADifferentSetStartsTheCountAgain(t *testing.T) { +func testADifferentSetStartsTheCountAgain(t *testing.T) { w := newWorld(t) given := holding(w, 5) w.give(given[:4]...) @@ -263,7 +263,7 @@ func TestADifferentSetStartsTheCountAgain(t *testing.T) { } } -func TestAdditionsAndChangesAreNeverDelayed(t *testing.T) { +func testAdditionsAndChangesAreNeverDelayed(t *testing.T) { w := newWorld(t) holding(w, 1) w.give(map[string]any{"as": "c1", "node": "anchor"}, map[string]any{"as": "c2"}) @@ -278,7 +278,7 @@ func TestAdditionsAndChangesAreNeverDelayed(t *testing.T) { } } -func TestTooManyWaitsForAPersonAndApproveRetiresExactlyThatSet(t *testing.T) { +func testTooManyWaitsForAPersonAndApproveRetiresExactlyThatSet(t *testing.T) { w, said := standingWorld(t) holding(w, 4) w.give() @@ -327,7 +327,7 @@ func TestTooManyWaitsForAPersonAndApproveRetiresExactlyThatSet(t *testing.T) { } } -func TestRejectedIsKeptAndNotAskedAgainUntilTheSetChanges(t *testing.T) { +func testRejectedIsKeptAndNotAskedAgainUntilTheSetChanges(t *testing.T) { w, said := standingWorld(t) given := holding(w, 4) w.give() @@ -372,7 +372,7 @@ func TestRejectedIsKeptAndNotAskedAgainUntilTheSetChanges(t *testing.T) { } } -func TestAWaitingSetAskedForAgainSettles(t *testing.T) { +func testAWaitingSetAskedForAgainSettles(t *testing.T) { w, said := standingWorld(t) given := holding(w, 4) w.give() @@ -387,7 +387,7 @@ func TestAWaitingSetAskedForAgainSettles(t *testing.T) { } } -func TestDeleteRemovesOnlyThatRetiredConsumer(t *testing.T) { +func testDeleteRemovesOnlyThatRetiredConsumer(t *testing.T) { w, said := standingWorld(t) given := holding(w, 5) w.give(given[:3]...) @@ -423,7 +423,7 @@ func TestDeleteRemovesOnlyThatRetiredConsumer(t *testing.T) { } } -func TestTheBound(t *testing.T) { +func testTheBound(t *testing.T) { h := &Harness{} h.init() for _, c := range []struct{ n, held int }{{1, 1}, {1, 2}, {1, 3}, {3, 7}, {3, 6}, {2, 5}} { @@ -438,7 +438,7 @@ func TestTheBound(t *testing.T) { } } -func TestARestartRetiresWhatTheBackendHoldsUnaskedAndAdoptsWhatItFindsDisabled(t *testing.T) { +func testARestartRetiresWhatTheBackendHoldsUnaskedAndAdoptsWhatItFindsDisabled(t *testing.T) { w, said := standingWorld(t) w.a.inv = Inventory{Active: []string{"kept", "orphan"}, Retired: []Retired{ {Consumer: "locked-before", SizeBytes: 7, Kind: KindConsumer}, @@ -458,7 +458,7 @@ func TestARestartRetiresWhatTheBackendHoldsUnaskedAndAdoptsWhatItFindsDisabled(t } } -func TestABackendThatCannotBeListedIsSaidAndAskedAgain(t *testing.T) { +func testABackendThatCannotBeListedIsSaidAndAskedAgain(t *testing.T) { w := newWorld(t) w.a.invErr = errors.New("connection refused") holding(w, 1) @@ -474,7 +474,7 @@ func TestABackendThatCannotBeListedIsSaidAndAskedAgain(t *testing.T) { } } -func TestTheToolsAnswer(t *testing.T) { +func testTheToolsAnswer(t *testing.T) { w, _ := standingWorld(t) holding(w, 4) w.give() @@ -512,7 +512,7 @@ func TestTheToolsAnswer(t *testing.T) { } } -func TestAFailingConsumerRetiredIsSaidRecovered(t *testing.T) { +func testAFailingConsumerRetiredIsSaidRecovered(t *testing.T) { w, said := standingWorld(t) given := holding(w, 2) w.a.held = false @@ -542,7 +542,7 @@ func everyWord(t *testing.T) (*world, *[]announced) { // A restarted provider that finds every consumer it holds asked for says once that nothing waits, so the controller // clears a waiting condition an earlier process raised and this one never knew (issue 937); it says it no more. -func TestARestartedProviderSaysOnceThatNothingWaits(t *testing.T) { +func testARestartedProviderSaysOnceThatNothingWaits(t *testing.T) { w, said := everyWord(t) holding(w, 2) w.pass() @@ -559,7 +559,7 @@ func TestARestartedProviderSaysOnceThatNothingWaits(t *testing.T) { } // A provider whose own set waits does not say that nothing waits. -func TestAProviderThatWaitsDoesNotSayNothingWaits(t *testing.T) { +func testAProviderThatWaitsDoesNotSayNothingWaits(t *testing.T) { w, said := everyWord(t) w.a.inv.Active = []string{"c1", "c2", "c3", "c4", "c5"} w.give(map[string]any{"as": "c1", "node": "anchor"}) @@ -573,3 +573,24 @@ func TestAProviderThatWaitsDoesNotSayNothingWaits(t *testing.T) { t.Fatal("nothing waited: the test does not hold the case") } } + +// TestRetirement holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestRetirement(t *testing.T) { + t.Run("ATransientEmptyListForFourPassesRetiresNothing", testATransientEmptyListForFourPassesRetiresNothing) + t.Run("FivePassesInTwentyFiveSecondsRetireNothingTenMinutesDo", testFivePassesInTwentyFiveSecondsRetireNothingTenMinutesDo) + t.Run("AStableSetIsRetiredAndAskedAgainReEnables", testAStableSetIsRetiredAndAskedAgainReEnables) + t.Run("AnUnreadablePassStartsTheCountAgain", testAnUnreadablePassStartsTheCountAgain) + t.Run("ADifferentSetStartsTheCountAgain", testADifferentSetStartsTheCountAgain) + t.Run("AdditionsAndChangesAreNeverDelayed", testAdditionsAndChangesAreNeverDelayed) + t.Run("TooManyWaitsForAPersonAndApproveRetiresExactlyThatSet", testTooManyWaitsForAPersonAndApproveRetiresExactlyThatSet) + t.Run("RejectedIsKeptAndNotAskedAgainUntilTheSetChanges", testRejectedIsKeptAndNotAskedAgainUntilTheSetChanges) + t.Run("AWaitingSetAskedForAgainSettles", testAWaitingSetAskedForAgainSettles) + t.Run("DeleteRemovesOnlyThatRetiredConsumer", testDeleteRemovesOnlyThatRetiredConsumer) + t.Run("TheBound", testTheBound) + t.Run("ARestartRetiresWhatTheBackendHoldsUnaskedAndAdoptsWhatItFindsDisabled", testARestartRetiresWhatTheBackendHoldsUnaskedAndAdoptsWhatItFindsDisabled) + t.Run("ABackendThatCannotBeListedIsSaidAndAskedAgain", testABackendThatCannotBeListedIsSaidAndAskedAgain) + t.Run("TheToolsAnswer", testTheToolsAnswer) + t.Run("AFailingConsumerRetiredIsSaidRecovered", testAFailingConsumerRetiredIsSaidRecovered) + t.Run("ARestartedProviderSaysOnceThatNothingWaits", testARestartedProviderSaysOnceThatNothingWaits) + t.Run("AProviderThatWaitsDoesNotSayNothingWaits", testAProviderThatWaitsDoesNotSayNothingWaits) +} diff --git a/go/provider/harness/standing_test.go b/go/provider/harness/standing_test.go index 106a9fb..44bd548 100644 --- a/go/provider/harness/standing_test.go +++ b/go/provider/harness/standing_test.go @@ -47,7 +47,7 @@ func events(said []announced) string { return strings.Join(out, ",") } -func TestAConsumerFailedForMinutesIsAnnouncedNamingItAndTheClass(t *testing.T) { +func testAConsumerFailedForMinutesIsAnnouncedNamingItAndTheClass(t *testing.T) { w, said := standingWorld(t) w.a.failing = errors.New(`token request failed: 401 {"error":"invalid_grant","error_description":"Invalid user credentials"}`) w.give(map[string]any{"as": "mesh_home_grafana", "node": "home-server"}) @@ -87,7 +87,7 @@ func TestAConsumerFailedForMinutesIsAnnouncedNamingItAndTheClass(t *testing.T) { } } -func TestOneSuccessBetweenFailuresStartsTheRunAgain(t *testing.T) { +func testOneSuccessBetweenFailuresStartsTheRunAgain(t *testing.T) { w, said := standingWorld(t) w.a.failing = errors.New("connection refused") w.give(map[string]any{"as": "a"}) @@ -104,7 +104,7 @@ func TestOneSuccessBetweenFailuresStartsTheRunAgain(t *testing.T) { // The check that failed for a day on 2026-10-05: clients already made, every minute's check refused // at the token. A check that cannot be asked is a failure too. -func TestACheckThatKeepsFailingIsAFailureToo(t *testing.T) { +func testACheckThatKeepsFailingIsAFailureToo(t *testing.T) { w, said := standingWorld(t) w.give(map[string]any{"as": "a"}) w.h.Reconcile(ctx) @@ -120,7 +120,7 @@ func TestACheckThatKeepsFailingIsAFailureToo(t *testing.T) { } } -func TestAnUnreadableSecretIsAnnouncedAsSuch(t *testing.T) { +func testAnUnreadableSecretIsAnnouncedAsSuch(t *testing.T) { w, said := standingWorld(t) w.give(map[string]any{"as": "a"}) os.Remove(w.dir + "/a.secret") @@ -130,7 +130,7 @@ func TestAnUnreadableSecretIsAnnouncedAsSuch(t *testing.T) { } } -func TestAConsumerNoLongerAskedForIsNoLongerFailing(t *testing.T) { +func testAConsumerNoLongerAskedForIsNoLongerFailing(t *testing.T) { w, said := standingWorld(t) w.a.failing = errors.New("boom") w.give(map[string]any{"as": "a"}, map[string]any{"as": "b"}) @@ -146,7 +146,7 @@ func TestAConsumerNoLongerAskedForIsNoLongerFailing(t *testing.T) { } } -func TestAnErrorIsClassedByItsWords(t *testing.T) { +func testAnErrorIsClassedByItsWords(t *testing.T) { for text, want := range map[string]string{ `Keycloak token request failed: 401 {"error":"invalid_grant"}`: ClassCredentials, `FATAL: password authentication failed for user "postgres"`: ClassCredentials, @@ -160,7 +160,7 @@ func TestAnErrorIsClassedByItsWords(t *testing.T) { } } -func TestAnAdapterThatClassesItsOwnErrorsIsBelieved(t *testing.T) { +func testAnAdapterThatClassesItsOwnErrorsIsBelieved(t *testing.T) { w, said := standingWorld(t) w.h.Adapter = classing{w.a} w.a.failing = errors.New("anything") @@ -177,7 +177,7 @@ func (classing) Class(error) string { return "its-own" } // A provider restarted after announcing a failure has forgotten it; its first success for each // consumer is announced, so the controller clears what it kept rather than naming it for ever. -func TestTheFirstSuccessSinceStartIsAnnouncedOnce(t *testing.T) { +func testTheFirstSuccessSinceStartIsAnnouncedOnce(t *testing.T) { w := newWorld(t) var said []announced w.h.Announce = func(e string, b map[string]any) { @@ -191,3 +191,15 @@ func TestTheFirstSuccessSinceStartIsAnnouncedOnce(t *testing.T) { t.Fatalf("%v", said) } } + +// TestStanding holds this file's cases, each a subtest of its own name (mesh-sdk's test budget: at most 500 unit tests). +func TestStanding(t *testing.T) { + t.Run("AConsumerFailedForMinutesIsAnnouncedNamingItAndTheClass", testAConsumerFailedForMinutesIsAnnouncedNamingItAndTheClass) + t.Run("OneSuccessBetweenFailuresStartsTheRunAgain", testOneSuccessBetweenFailuresStartsTheRunAgain) + t.Run("ACheckThatKeepsFailingIsAFailureToo", testACheckThatKeepsFailingIsAFailureToo) + t.Run("AnUnreadableSecretIsAnnouncedAsSuch", testAnUnreadableSecretIsAnnouncedAsSuch) + t.Run("AConsumerNoLongerAskedForIsNoLongerFailing", testAConsumerNoLongerAskedForIsNoLongerFailing) + t.Run("AnErrorIsClassedByItsWords", testAnErrorIsClassedByItsWords) + t.Run("AnAdapterThatClassesItsOwnErrorsIsBelieved", testAnAdapterThatClassesItsOwnErrorsIsBelieved) + t.Run("TheFirstSuccessSinceStartIsAnnouncedOnce", testTheFirstSuccessSinceStartIsAnnouncedOnce) +}