|
| 1 | +package solution |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "errors" |
| 6 | + "fmt" |
| 7 | + "os" |
| 8 | + "strings" |
| 9 | + "testing" |
| 10 | + |
| 11 | + "github.com/codefly-dev/core/resources" |
| 12 | + codefly "github.com/codefly-dev/sdk-go" |
| 13 | +) |
| 14 | + |
| 15 | +// testModule is the module every test in this package runs as, pinned once for |
| 16 | +// the process the way a Codefly flow pins a service's: the SDK refuses every |
| 17 | +// endpoint query until Init has pinned the consumer, and a pin cannot be moved |
| 18 | +// afterwards, so there is exactly one for the whole binary. Neutral for the |
| 19 | +// same reason testSolutionID is. |
| 20 | +const testModule = "widgets" |
| 21 | + |
| 22 | +// TestMain identifies the test process to the SDK before anything resolves. |
| 23 | +// |
| 24 | +// Not a convenience: since sdk-go v0.3.1 an unpinned process resolves no |
| 25 | +// endpoint at all, so without this every test that reads an address would be |
| 26 | +// asserting on a refusal it never asked about. The boot pins it too — start() |
| 27 | +// calls Init — and that path is what TestABootThatNamesNoModuleIsRefused |
| 28 | +// drives. |
| 29 | +func TestMain(m *testing.M) { |
| 30 | + if err := os.Setenv(resources.ModulePrefix, testModule); err != nil { |
| 31 | + fmt.Fprintln(os.Stderr, err) |
| 32 | + os.Exit(1) |
| 33 | + } |
| 34 | + if _, err := codefly.Init(context.Background()); err != nil { |
| 35 | + fmt.Fprintf(os.Stderr, "codefly.Init for the test process: %v\n", err) |
| 36 | + os.Exit(1) |
| 37 | + } |
| 38 | + os.Exit(m.Run()) |
| 39 | +} |
| 40 | + |
| 41 | +// TestABootThatNamesNoModuleIsRefused: a process the SDK cannot identify does |
| 42 | +// not serve, and the refusal says why rather than naming the first endpoint |
| 43 | +// that came back empty. |
| 44 | +// |
| 45 | +// The boot used to call LoadEnvironmentVariables and nothing else. Against |
| 46 | +// sdk-go v0.3.1 and later that resolves no endpoint — the gateway, the |
| 47 | +// frontend and this service's own port all come back empty — and validate() |
| 48 | +// then sent the operator to inspect endpoint provisioning for a variable that |
| 49 | +// is not an endpoint at all. |
| 50 | +func TestABootThatNamesNoModuleIsRefused(t *testing.T) { |
| 51 | + for _, tc := range []struct { |
| 52 | + name, module string |
| 53 | + }{ |
| 54 | + // The process is already pinned to testModule, so an unset carrier |
| 55 | + // is the SDK's drift refusal here; in a fresh process it is |
| 56 | + // ErrConsumerNotIdentified. Both are the same refusal to this boot. |
| 57 | + {"no module named", ""}, |
| 58 | + {"a module other than the one this process was pinned to", "someone-else"}, |
| 59 | + } { |
| 60 | + t.Run(tc.name, func(t *testing.T) { |
| 61 | + t.Setenv(resources.ModulePrefix, tc.module) |
| 62 | + // Every endpoint overridden, so nothing else can be the reason. |
| 63 | + t.Setenv("PORT", "8443") |
| 64 | + t.Setenv("GATEWAY_URL", "https://gateway.cell") |
| 65 | + t.Setenv(CredentialMintURLEnvironmentVariable, "https://gateway.cell"+credentialMintPath) |
| 66 | + t.Cleanup(func() { _ = codefly.LoadEnvironmentVariables() }) |
| 67 | + |
| 68 | + _, initErr := codefly.Init(context.Background()) |
| 69 | + if initErr == nil { |
| 70 | + t.Fatal("codefly.Init accepted a process naming no module, or another one: the SDK's contract this boot relies on has changed") |
| 71 | + } |
| 72 | + cfg := loadConfig(context.Background(), testSolutionID, initErr) |
| 73 | + if cfg.environmentLoadErr != nil { |
| 74 | + t.Errorf("an identity refusal was filed as an environment load failure: %v", cfg.environmentLoadErr) |
| 75 | + } |
| 76 | + err := cfg.validate() |
| 77 | + if err == nil { |
| 78 | + t.Fatal("validate() accepted a boot the SDK could not identify") |
| 79 | + } |
| 80 | + if !strings.Contains(err.Error(), resources.ModulePrefix) { |
| 81 | + t.Errorf("the refusal does not name %s, the variable that fixes it: %v", resources.ModulePrefix, err) |
| 82 | + } |
| 83 | + if !errors.Is(err, resources.ErrConsumerNotIdentified) && !errors.Is(err, codefly.ErrAuthorityValueChanged) { |
| 84 | + t.Errorf("the refusal dropped the SDK's sentinel: %v", err) |
| 85 | + } |
| 86 | + }) |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +// TestTheBootIdentifiesItselfToTheSDK drives the same refusal through start(), |
| 91 | +// with every other value a boot needs in place and every endpoint overridden. |
| 92 | +// |
| 93 | +// That is what makes it a test of the boot rather than of validate(): with the |
| 94 | +// addresses overridden, a boot that only loaded the carriers — what start() |
| 95 | +// did before it called Init — would come up, because nothing it reads in this |
| 96 | +// test goes through endpoint resolution. It must not: the SDK's contract is |
| 97 | +// that a process failing Init does not serve. |
| 98 | +func TestTheBootIdentifiesItselfToTheSDK(t *testing.T) { |
| 99 | + mint := newHostMint(t, &hostMint{}) |
| 100 | + t.Setenv(resources.ModulePrefix, "") |
| 101 | + t.Cleanup(func() { _ = codefly.LoadEnvironmentVariables() }) |
| 102 | + |
| 103 | + err := bootFails(t, New(Manifest{ID: testSolutionID}), mint) |
| 104 | + if !strings.Contains(err.Error(), resources.ModulePrefix) { |
| 105 | + t.Errorf("the boot was refused, but not for naming no module: %v", err) |
| 106 | + } |
| 107 | + mint.mu.Lock() |
| 108 | + reached := len(mint.paths) |
| 109 | + mint.mu.Unlock() |
| 110 | + if got := reached; got != 0 { |
| 111 | + t.Errorf("the boot reached the mint %d time(s) before refusing: a process the SDK cannot identify must not present its projected token anywhere", got) |
| 112 | + } |
| 113 | +} |
0 commit comments