J3: mesh-sdk within its test budget, each file of declaration and the provider harness one test of subtests
The unit test functions fall from 587 to 476 (the budget is 500): the cases of each of twenty test files in declaration and provider/harness become subtests of one test named for the file, under their own names, their bodies unchanged. Integration tests stay at 30 (budget 50). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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-<module>": `"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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user