| 1 | package memoria |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/base64" |
| 6 | "io" |
| 7 | "log/slog" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | |
| 11 | "amadan.net/oficina/memoria/internal/memoria/provider" |
| 12 | ) |
| 13 | |
| 14 | func baseEnv() map[string]string { |
| 15 | return map[string]string{ |
| 16 | "MEMORIA_DEK": base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{2}, 32)), |
| 17 | "MEMORIA_INSTANCE_KEY": "test-only-instance", |
| 18 | "MEMORIA_OWNERS": "alice@example.test", |
| 19 | "MEMORIA_ALLOW_LOGGED_MAIL": "1", |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | // Decision 4: the model layer is the team owner's opt-in, and it is |
| 24 | // switched by one word. Each refusal below is paired with its presence |
| 25 | // half, because a switch that refuses everything passes every refusal. |
| 26 | func TestTheModelSwitch(t *testing.T) { |
| 27 | for _, c := range []struct { |
| 28 | name string |
| 29 | ai, key string |
| 30 | wantAI bool |
| 31 | wantErr string |
| 32 | }{ |
| 33 | {"unset is off", "", "", false, ""}, |
| 34 | // Presence of a credential is not consent; the switch is. |
| 35 | {"a key with AI unset is still off", "", "k", false, ""}, |
| 36 | {"on with a key is on", "on", "k", true, ""}, |
| 37 | {"on without a key is refused", "on", "", false, "MEMORIA_TENSORX_KEY"}, |
| 38 | // A typo must not be read as either answer. |
| 39 | {"'true' is refused, not on", "true", "k", false, "MEMORIA_AI"}, |
| 40 | {"'On' is refused, not on", "On", "k", false, "MEMORIA_AI"}, |
| 41 | {"'off' is refused, not off", "off", "k", false, "MEMORIA_AI"}, |
| 42 | } { |
| 43 | t.Run(c.name, func(t *testing.T) { |
| 44 | env := baseEnv() |
| 45 | env["MEMORIA_AI"], env["MEMORIA_TENSORX_KEY"] = c.ai, c.key |
| 46 | cfg, err := LoadConfig(func(k string) string { return env[k] }) |
| 47 | if c.wantErr != "" { |
| 48 | if err == nil || !strings.Contains(err.Error(), c.wantErr) { |
| 49 | t.Fatalf("err = %v, want one naming %s", err, c.wantErr) |
| 50 | } |
| 51 | // The credential must never be in the refusal. |
| 52 | if c.key != "" && strings.Contains(err.Error(), c.key+"\"") { |
| 53 | t.Errorf("refusal carries the key: %v", err) |
| 54 | } |
| 55 | return |
| 56 | } |
| 57 | if err != nil { |
| 58 | t.Fatalf("err = %v", err) |
| 59 | } |
| 60 | if cfg.AI != c.wantAI { |
| 61 | t.Errorf("AI = %v, want %v", cfg.AI, c.wantAI) |
| 62 | } |
| 63 | // The credential that reaches the client is the one the owner |
| 64 | // configured. A review replaced it with "wrong-key" in |
| 65 | // LoadConfig and every case above stayed green. |
| 66 | if c.wantAI && cfg.TensorXKey != c.key { |
| 67 | t.Errorf("loaded a different credential from the one configured") |
| 68 | } |
| 69 | }) |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | // WireModel sets an extractor exactly when AI is on, and it is TensorX. |
| 74 | func TestWireModelOnlyWhenOn(t *testing.T) { |
| 75 | a := &app{store: &Store{}, logger: slog.New(slog.NewTextHandler(io.Discard, nil))} |
| 76 | if err := WireModel(a, Config{AI: false, TensorXKey: "k"}); err != nil { |
| 77 | t.Fatal(err) |
| 78 | } |
| 79 | if a.store.extractor != nil { |
| 80 | t.Fatal("AI off, but an extractor was wired: content could leave") |
| 81 | } |
| 82 | if err := WireModel(a, Config{AI: true, TensorXKey: "k"}); err != nil { |
| 83 | t.Fatal(err) |
| 84 | } |
| 85 | if _, ok := a.store.extractor.(*provider.Client); !ok { |
| 86 | t.Fatalf("AI on wired %T, want *provider.Client", a.store.extractor) |
| 87 | } |
| 88 | // And AI on with no credential fails at boot, not on the first search: |
| 89 | // provider.New refuses an empty key, and WireModel passes that up. |
| 90 | b := &app{store: &Store{}, logger: slog.New(slog.NewTextHandler(io.Discard, nil))} |
| 91 | if err := WireModel(b, Config{AI: true}); err == nil || b.store.extractor != nil { |
| 92 | t.Errorf("wired a model with no credential: err=%v", err) |
| 93 | } |
| 94 | } |
| 95 | |