feat(broker): freeze broker contracts and boundaries
- commit
- 01ca71dfc1eff8552064efd175b6eb892a1dfe64
- author
- brice · · 2026-09-16 07:13 UTC
- parent
- 25169d5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +2953 -1
M boundary_test.go +61 -0
| @@ -100,6 +100,36 @@ func TestImportBoundaryNegativeFixtures(t *testing.T) { | |||
| 100 | 100 | {"other adapter rejects usecase", modulePath + "/internal/adapters/ipc", modulePath + "/internal/usecase/client", false, false}, | |
| 101 | 101 | {"usecase test may use wire fixture", modulePath + "/internal/usecase/client", modulePath + "/internal/protocol/wire", true, true}, | |
| 102 | 102 | {"pkg test still rejects internal", modulePath + "/pkg/rawterm", modulePath + "/internal/domain", true, false}, | |
| 103 | + | // P1.1 broker ownership: broker consumes ports/protocol/domain | |
| 104 | + | // (catalogue counts as protocol family) and no sibling use case. | |
| 105 | + | {"broker accepts ports", modulePath + "/internal/usecase/broker", modulePath + "/internal/ports", false, true}, | |
| 106 | + | {"broker accepts protocol", modulePath + "/internal/usecase/broker", modulePath + "/internal/protocol", false, true}, | |
| 107 | + | {"broker accepts domain", modulePath + "/internal/usecase/broker", modulePath + "/internal/domain", false, true}, | |
| 108 | + | {"broker accepts catalogue", modulePath + "/internal/usecase/broker", modulePath + "/internal/protocol/catalogue", false, true}, | |
| 109 | + | {"broker accepts own subpackage", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/broker/streams", false, true}, | |
| 110 | + | {"broker subpackage accepts broker root", modulePath + "/internal/usecase/broker/streams", modulePath + "/internal/usecase/broker", false, true}, | |
| 111 | + | {"broker rejects client", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/client", false, false}, | |
| 112 | + | {"broker rejects daemon", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/daemon", false, false}, | |
| 113 | + | {"broker rejects remotes", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/remotes", false, false}, | |
| 114 | + | {"broker rejects sibling usecase", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/picker", false, false}, | |
| 115 | + | {"broker rejects adapter", modulePath + "/internal/usecase/broker", modulePath + "/internal/adapters/ipc", false, false}, | |
| 116 | + | {"broker rejects broker adapter", modulePath + "/internal/usecase/broker", modulePath + "/internal/adapters/remote", false, false}, | |
| 117 | + | {"broker rejects wire", modulePath + "/internal/usecase/broker", modulePath + "/internal/protocol/wire", false, false}, | |
| 118 | + | {"broker rejects app", modulePath + "/internal/usecase/broker", modulePath + "/internal/app", false, false}, | |
| 119 | + | {"broker rejects persist", modulePath + "/internal/usecase/broker", modulePath + "/internal/persist", false, false}, | |
| 120 | + | // P1.1 client/daemon must not own broker, each other, or remote discovery. | |
| 121 | + | {"client rejects daemon", modulePath + "/internal/usecase/client", modulePath + "/internal/usecase/daemon", false, false}, | |
| 122 | + | {"client rejects broker", modulePath + "/internal/usecase/client", modulePath + "/internal/usecase/broker", false, false}, | |
| 123 | + | {"client rejects remotes", modulePath + "/internal/usecase/client", modulePath + "/internal/usecase/remotes", false, false}, | |
| 124 | + | {"client rejects broker adapter", modulePath + "/internal/usecase/client", modulePath + "/internal/adapters/remote", false, false}, | |
| 125 | + | {"daemon rejects client", modulePath + "/internal/usecase/daemon", modulePath + "/internal/usecase/client", false, false}, | |
| 126 | + | {"daemon rejects broker", modulePath + "/internal/usecase/daemon", modulePath + "/internal/usecase/broker", false, false}, | |
| 127 | + | {"daemon rejects remotes registry", modulePath + "/internal/usecase/daemon", modulePath + "/internal/usecase/remotes", false, false}, | |
| 128 | + | {"daemon rejects remote adapter", modulePath + "/internal/usecase/daemon", modulePath + "/internal/adapters/remote", false, false}, | |
| 129 | + | {"daemon rejects app", modulePath + "/internal/usecase/daemon", modulePath + "/internal/app", false, false}, | |
| 130 | + | {"remotes rejects client", modulePath + "/internal/usecase/remotes", modulePath + "/internal/usecase/client", false, false}, | |
| 131 | + | {"remotes rejects daemon", modulePath + "/internal/usecase/remotes", modulePath + "/internal/usecase/daemon", false, false}, | |
| 132 | + | {"remotes rejects broker", modulePath + "/internal/usecase/remotes", modulePath + "/internal/usecase/broker", false, false}, | |
| 103 | 133 | } | |
| 104 | 134 | for _, tt := range tests { | |
| 105 | 135 | t.Run(tt.name, func(t *testing.T) { | |
| @@ -185,9 +215,40 @@ func dependencyAllowed(source, target string, testFile bool) (bool, error) { | |||
| 185 | 215 | if source == modulePath+"/internal/adapters/snapshot" && target == modulePath+"/internal/usecase/snapshot" { | |
| 186 | 216 | return true, nil | |
| 187 | 217 | } | |
| 218 | + | if packageImportDenied(source, target) { | |
| 219 | + | return false, nil | |
| 220 | + | } | |
| 188 | 221 | return productionDependencies[sourceLayer][targetLayer], nil | |
| 189 | 222 | } | |
| 190 | 223 | ||
| 224 | + | // packageImportDenied encodes ADR 001 ownership at package granularity for | |
| 225 | + | // production files. Layer rules alone permit any usecase-to-usecase import; | |
| 226 | + | // these denies keep broker, client, daemon, and remotes from owning each | |
| 227 | + | // other, and keep the broker free of every sibling use case. Test files stay | |
| 228 | + | // exempt so composition tests can wire adapters and orchestrators together. | |
| 229 | + | func packageImportDenied(source, target string) bool { | |
| 230 | + | brokerPkg := modulePath + "/internal/usecase/broker" | |
| 231 | + | clientPkg := modulePath + "/internal/usecase/client" | |
| 232 | + | daemonPkg := modulePath + "/internal/usecase/daemon" | |
| 233 | + | remotesPkg := modulePath + "/internal/usecase/remotes" | |
| 234 | + | usecasePrefix := modulePath + "/internal/usecase/" | |
| 235 | + | under := func(path, root string) bool { | |
| 236 | + | return path == root || strings.HasPrefix(path, root+"/") | |
| 237 | + | } | |
| 238 | + | switch { | |
| 239 | + | case under(source, brokerPkg): | |
| 240 | + | return !under(target, brokerPkg) && strings.HasPrefix(target, usecasePrefix) | |
| 241 | + | case under(source, clientPkg): | |
| 242 | + | return under(target, daemonPkg) || under(target, brokerPkg) || under(target, remotesPkg) | |
| 243 | + | case under(source, daemonPkg): | |
| 244 | + | return under(target, clientPkg) || under(target, brokerPkg) || under(target, remotesPkg) | |
| 245 | + | case under(source, remotesPkg): | |
| 246 | + | return under(target, clientPkg) || under(target, daemonPkg) || under(target, brokerPkg) | |
| 247 | + | default: | |
| 248 | + | return false | |
| 249 | + | } | |
| 250 | + | } | |
| 251 | + | ||
| 191 | 252 | func testSupportDependencyAllowed(source string, target packageLayer) bool { | |
| 192 | 253 | switch { | |
| 193 | 254 | case source == modulePath+"/internal/ports/mocks": | |
| @@ -100,6 +100,36 @@ func TestImportBoundaryNegativeFixtures(t *testing.T) { | |||||
| 100 | {"other adapter rejects usecase", modulePath + "/internal/adapters/ipc", modulePath + "/internal/usecase/client", false, false}, | 100 | {"other adapter rejects usecase", modulePath + "/internal/adapters/ipc", modulePath + "/internal/usecase/client", false, false}, | ||
| 101 | {"usecase test may use wire fixture", modulePath + "/internal/usecase/client", modulePath + "/internal/protocol/wire", true, true}, | 101 | {"usecase test may use wire fixture", modulePath + "/internal/usecase/client", modulePath + "/internal/protocol/wire", true, true}, | ||
| 102 | {"pkg test still rejects internal", modulePath + "/pkg/rawterm", modulePath + "/internal/domain", true, false}, | 102 | {"pkg test still rejects internal", modulePath + "/pkg/rawterm", modulePath + "/internal/domain", true, false}, | ||
| 103 | + | // P1.1 broker ownership: broker consumes ports/protocol/domain | |||
| 104 | + | // (catalogue counts as protocol family) and no sibling use case. | |||
| 105 | + | {"broker accepts ports", modulePath + "/internal/usecase/broker", modulePath + "/internal/ports", false, true}, | |||
| 106 | + | {"broker accepts protocol", modulePath + "/internal/usecase/broker", modulePath + "/internal/protocol", false, true}, | |||
| 107 | + | {"broker accepts domain", modulePath + "/internal/usecase/broker", modulePath + "/internal/domain", false, true}, | |||
| 108 | + | {"broker accepts catalogue", modulePath + "/internal/usecase/broker", modulePath + "/internal/protocol/catalogue", false, true}, | |||
| 109 | + | {"broker accepts own subpackage", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/broker/streams", false, true}, | |||
| 110 | + | {"broker subpackage accepts broker root", modulePath + "/internal/usecase/broker/streams", modulePath + "/internal/usecase/broker", false, true}, | |||
| 111 | + | {"broker rejects client", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/client", false, false}, | |||
| 112 | + | {"broker rejects daemon", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/daemon", false, false}, | |||
| 113 | + | {"broker rejects remotes", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/remotes", false, false}, | |||
| 114 | + | {"broker rejects sibling usecase", modulePath + "/internal/usecase/broker", modulePath + "/internal/usecase/picker", false, false}, | |||
| 115 | + | {"broker rejects adapter", modulePath + "/internal/usecase/broker", modulePath + "/internal/adapters/ipc", false, false}, | |||
| 116 | + | {"broker rejects broker adapter", modulePath + "/internal/usecase/broker", modulePath + "/internal/adapters/remote", false, false}, | |||
| 117 | + | {"broker rejects wire", modulePath + "/internal/usecase/broker", modulePath + "/internal/protocol/wire", false, false}, | |||
| 118 | + | {"broker rejects app", modulePath + "/internal/usecase/broker", modulePath + "/internal/app", false, false}, | |||
| 119 | + | {"broker rejects persist", modulePath + "/internal/usecase/broker", modulePath + "/internal/persist", false, false}, | |||
| 120 | + | // P1.1 client/daemon must not own broker, each other, or remote discovery. | |||
| 121 | + | {"client rejects daemon", modulePath + "/internal/usecase/client", modulePath + "/internal/usecase/daemon", false, false}, | |||
| 122 | + | {"client rejects broker", modulePath + "/internal/usecase/client", modulePath + "/internal/usecase/broker", false, false}, | |||
| 123 | + | {"client rejects remotes", modulePath + "/internal/usecase/client", modulePath + "/internal/usecase/remotes", false, false}, | |||
| 124 | + | {"client rejects broker adapter", modulePath + "/internal/usecase/client", modulePath + "/internal/adapters/remote", false, false}, | |||
| 125 | + | {"daemon rejects client", modulePath + "/internal/usecase/daemon", modulePath + "/internal/usecase/client", false, false}, | |||
| 126 | + | {"daemon rejects broker", modulePath + "/internal/usecase/daemon", modulePath + "/internal/usecase/broker", false, false}, | |||
| 127 | + | {"daemon rejects remotes registry", modulePath + "/internal/usecase/daemon", modulePath + "/internal/usecase/remotes", false, false}, | |||
| 128 | + | {"daemon rejects remote adapter", modulePath + "/internal/usecase/daemon", modulePath + "/internal/adapters/remote", false, false}, | |||
| 129 | + | {"daemon rejects app", modulePath + "/internal/usecase/daemon", modulePath + "/internal/app", false, false}, | |||
| 130 | + | {"remotes rejects client", modulePath + "/internal/usecase/remotes", modulePath + "/internal/usecase/client", false, false}, | |||
| 131 | + | {"remotes rejects daemon", modulePath + "/internal/usecase/remotes", modulePath + "/internal/usecase/daemon", false, false}, | |||
| 132 | + | {"remotes rejects broker", modulePath + "/internal/usecase/remotes", modulePath + "/internal/usecase/broker", false, false}, | |||
| 103 | } | 133 | } | ||
| 104 | for _, tt := range tests { | 134 | for _, tt := range tests { | ||
| 105 | t.Run(tt.name, func(t *testing.T) { | 135 | t.Run(tt.name, func(t *testing.T) { | ||
| @@ -185,9 +215,40 @@ func dependencyAllowed(source, target string, testFile bool) (bool, error) { | |||||
| 185 | if source == modulePath+"/internal/adapters/snapshot" && target == modulePath+"/internal/usecase/snapshot" { | 215 | if source == modulePath+"/internal/adapters/snapshot" && target == modulePath+"/internal/usecase/snapshot" { | ||
| 186 | return true, nil | 216 | return true, nil | ||
| 187 | } | 217 | } | ||
| 218 | + | if packageImportDenied(source, target) { | |||
| 219 | + | return false, nil | |||
| 220 | + | } | |||
| 188 | return productionDependencies[sourceLayer][targetLayer], nil | 221 | return productionDependencies[sourceLayer][targetLayer], nil | ||
| 189 | } | 222 | } | ||
| 190 | 223 | ||||
| 224 | + | // packageImportDenied encodes ADR 001 ownership at package granularity for | |||
| 225 | + | // production files. Layer rules alone permit any usecase-to-usecase import; | |||
| 226 | + | // these denies keep broker, client, daemon, and remotes from owning each | |||
| 227 | + | // other, and keep the broker free of every sibling use case. Test files stay | |||
| 228 | + | // exempt so composition tests can wire adapters and orchestrators together. | |||
| 229 | + | func packageImportDenied(source, target string) bool { | |||
| 230 | + | brokerPkg := modulePath + "/internal/usecase/broker" | |||
| 231 | + | clientPkg := modulePath + "/internal/usecase/client" | |||
| 232 | + | daemonPkg := modulePath + "/internal/usecase/daemon" | |||
| 233 | + | remotesPkg := modulePath + "/internal/usecase/remotes" | |||
| 234 | + | usecasePrefix := modulePath + "/internal/usecase/" | |||
| 235 | + | under := func(path, root string) bool { | |||
| 236 | + | return path == root || strings.HasPrefix(path, root+"/") | |||
| 237 | + | } | |||
| 238 | + | switch { | |||
| 239 | + | case under(source, brokerPkg): | |||
| 240 | + | return !under(target, brokerPkg) && strings.HasPrefix(target, usecasePrefix) | |||
| 241 | + | case under(source, clientPkg): | |||
| 242 | + | return under(target, daemonPkg) || under(target, brokerPkg) || under(target, remotesPkg) | |||
| 243 | + | case under(source, daemonPkg): | |||
| 244 | + | return under(target, clientPkg) || under(target, brokerPkg) || under(target, remotesPkg) | |||
| 245 | + | case under(source, remotesPkg): | |||
| 246 | + | return under(target, clientPkg) || under(target, daemonPkg) || under(target, brokerPkg) | |||
| 247 | + | default: | |||
| 248 | + | return false | |||
| 249 | + | } | |||
| 250 | + | } | |||
| 251 | + | ||||
| 191 | func testSupportDependencyAllowed(source string, target packageLayer) bool { | 252 | func testSupportDependencyAllowed(source string, target packageLayer) bool { | ||
| 192 | switch { | 253 | switch { | ||
| 193 | case source == modulePath+"/internal/ports/mocks": | 254 | case source == modulePath+"/internal/ports/mocks": | ||
M docs/architecture.md +1 -1
| @@ -14,7 +14,7 @@ vev uses a hexagonal core with typed session messages at the client and daemon b | |||
| 14 | 14 | carrying `Envelope{Payload}` (one complete serialized directional | |
| 15 | 15 | envelope, no type byte). | |
| 16 | 16 | - `internal/ports`: application-facing interfaces and the values required by those interfaces. It contains no codecs, raw frames, environment policy, or worker implementations. | |
| 17 | - | - `internal/usecase`: client, daemon, and supporting application behavior. Production use cases may consume semantic protocol packages but never `protocol/wire` or concrete adapters. | |
| 17 | + | - `internal/usecase`: client, daemon, broker, and supporting application behavior. Production use cases may consume semantic protocol packages but never `protocol/wire` or concrete adapters. The broker may use its own subpackages but cannot import sibling use cases. Client, daemon, and remote-registry use cases cannot own or import each other; they collaborate only through ports and application composition. | |
| 18 | 18 | - `internal/adapters/sessionwire`: translates between typed session | |
| 19 | 19 | connections and raw wire transports: directional `oneof` envelope | |
| 20 | 20 | wrapping/unwrapping, the staged preamble state machine | |
| @@ -14,7 +14,7 @@ vev uses a hexagonal core with typed session messages at the client and daemon b | |||||
| 14 | carrying `Envelope{Payload}` (one complete serialized directional | 14 | carrying `Envelope{Payload}` (one complete serialized directional | ||
| 15 | envelope, no type byte). | 15 | envelope, no type byte). | ||
| 16 | - `internal/ports`: application-facing interfaces and the values required by those interfaces. It contains no codecs, raw frames, environment policy, or worker implementations. | 16 | - `internal/ports`: application-facing interfaces and the values required by those interfaces. It contains no codecs, raw frames, environment policy, or worker implementations. | ||
| 17 | - | - `internal/usecase`: client, daemon, and supporting application behavior. Production use cases may consume semantic protocol packages but never `protocol/wire` or concrete adapters. | 17 | + | - `internal/usecase`: client, daemon, broker, and supporting application behavior. Production use cases may consume semantic protocol packages but never `protocol/wire` or concrete adapters. The broker may use its own subpackages but cannot import sibling use cases. Client, daemon, and remote-registry use cases cannot own or import each other; they collaborate only through ports and application composition. |
| 18 | - `internal/adapters/sessionwire`: translates between typed session | 18 | - `internal/adapters/sessionwire`: translates between typed session | ||
| 19 | connections and raw wire transports: directional `oneof` envelope | 19 | connections and raw wire transports: directional `oneof` envelope | ||
| 20 | wrapping/unwrapping, the staged preamble state machine | 20 | wrapping/unwrapping, the staged preamble state machine | ||
A internal/app/connectivity_matrix.go +229 -0
| @@ -0,0 +1,229 @@ | |||
| 1 | + | package app | |
| 2 | + | ||
| 3 | + | // Connectivity migration matrix (Plan 001 P1.2). | |
| 4 | + | // | |
| 5 | + | // Every CLI entry point has exactly one owner. After the P7 cutover, all | |
| 6 | + | // ordinary user-facing operations reach daemons through the per-user | |
| 7 | + | // connection broker; narrowly scoped daemon-facing transport helpers stay | |
| 8 | + | // infrastructure and never become alternate client façades. | |
| 9 | + | // | |
| 10 | + | // Ownership rules pinned here: | |
| 11 | + | // - Broker-only entries must not keep a direct daemon dialer or a second | |
| 12 | + | // host-store writer after cutover. Their CurrentDebt lists today's | |
| 13 | + | // direct-connectivity symbols to remove in the same change that | |
| 14 | + | // activates the broker owner. | |
| 15 | + | // - Broker never owns session persistence. Offline session mutation/list | |
| 16 | + | // paths (PersistMutation) become daemon-owned operations reached through | |
| 17 | + | // the broker; the persist/snapshot packages stay daemon-side. | |
| 18 | + | // - Observation never starts a broker, never undoes an explicit | |
| 19 | + | // daemon-stop, and never creates a session attachment. | |
| 20 | + | // - Transport helpers terminate transport only. They never own host | |
| 21 | + | // configuration, navigation, sessions, or picker state. | |
| 22 | + | // | |
| 23 | + | // The accompanying test fails when a command kind lacks a row, when a | |
| 24 | + | // broker-only row declares no migration debt, when an infra row gains store | |
| 25 | + | // or persistence ownership, and when direct-dial, host-store, or offline | |
| 26 | + | // persistence symbols appear in files outside the declared debt set. | |
| 27 | + | ||
| 28 | + | type connectivityOwner uint8 | |
| 29 | + | ||
| 30 | + | const ( | |
| 31 | + | // connectivityBrokerOnly marks ordinary user-facing operations that go | |
| 32 | + | // through the broker façade after cutover. | |
| 33 | + | connectivityBrokerOnly connectivityOwner = iota + 1 | |
| 34 | + | // connectivityTransportInfra marks narrowly scoped daemon-facing | |
| 35 | + | // transport helpers: process mechanics and carriage terminators that | |
| 36 | + | // must never become client façades. | |
| 37 | + | connectivityTransportInfra | |
| 38 | + | // connectivityLocalOnly marks commands with no daemon or session | |
| 39 | + | // connectivity at all. | |
| 40 | + | connectivityLocalOnly | |
| 41 | + | ) | |
| 42 | + | ||
| 43 | + | func (o connectivityOwner) String() string { | |
| 44 | + | switch o { | |
| 45 | + | case connectivityBrokerOnly: | |
| 46 | + | return "broker-only" | |
| 47 | + | case connectivityTransportInfra: | |
| 48 | + | return "transport-infra" | |
| 49 | + | case connectivityLocalOnly: | |
| 50 | + | return "local-only" | |
| 51 | + | default: | |
| 52 | + | return "unknown" | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | // connectivityEntry is one row of the migration matrix. Kind is the cmdKind | |
| 57 | + | // identifier from run.go (for example "kindAttach"). | |
| 58 | + | type connectivityEntry struct { | |
| 59 | + | Kind string | |
| 60 | + | // Summary names the user-visible operation. | |
| 61 | + | Summary string | |
| 62 | + | Owner connectivityOwner | |
| 63 | + | // DirectDialDebt lists today's direct-daemon dial symbols that the | |
| 64 | + | // cutover removes for broker-only entries. Empty for infra/local rows. | |
| 65 | + | DirectDialDebt []string | |
| 66 | + | // HostStoreWrite reports a RemoteHostStore constructor/writer today. | |
| 67 | + | // Only the broker owns the host store after cutover. | |
| 68 | + | HostStoreWrite bool | |
| 69 | + | // PersistMutation reports an offline session persistence read/write | |
| 70 | + | // today. These become daemon-owned operations through the broker. | |
| 71 | + | PersistMutation bool | |
| 72 | + | // Notes records cutover direction and non-obvious boundaries. | |
| 73 | + | Notes string | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | // connectivityMatrix classifies every cmdKind exactly once. | |
| 77 | + | var connectivityMatrix = []connectivityEntry{ | |
| 78 | + | { | |
| 79 | + | Kind: "kindAttach", | |
| 80 | + | Summary: "interactive terminal attach/new/ephemeral, incl. remote attach, detached creation, attach preflight", | |
| 81 | + | Owner: connectivityBrokerOnly, | |
| 82 | + | DirectDialDebt: []string{ | |
| 83 | + | "localDaemonDialer.Dial (run.go)", | |
| 84 | + | "dialOnlyLocalDialer.Dial inventory control (run.go)", | |
| 85 | + | "ensureDaemonWithLifecycle detached creation (run.go createDetachedLocalSession)", | |
| 86 | + | "registry.ResolveEndpoint remote carriage (run.go runAttachWithDeps)", | |
| 87 | + | "listSessionsWithDialer attach preflight (attach_preflight.go sessionExists)", | |
| 88 | + | }, | |
| 89 | + | Notes: "vev without arguments keeps creating an ephemeral session; creation is expressed as client navigation through the broker.", | |
| 90 | + | }, | |
| 91 | + | { | |
| 92 | + | Kind: "kindList", | |
| 93 | + | Summary: "session list: local, per-host, and --all", | |
| 94 | + | Owner: connectivityBrokerOnly, | |
| 95 | + | DirectDialDebt: []string{ | |
| 96 | + | "realDial listSessionsWithDialer (run.go)", | |
| 97 | + | "waitForDaemonOrLifecycle list path (run.go)", | |
| 98 | + | "RemoteCatalogClient.List over SSH (remote_hosts.go listAllSessions/listOneRemoteHost)", | |
| 99 | + | }, | |
| 100 | + | PersistMutation: true, | |
| 101 | + | Notes: "Offline persist.LoadReadOnly list path becomes a daemon-owned operation through the broker; observation never attaches.", | |
| 102 | + | }, | |
| 103 | + | { | |
| 104 | + | Kind: "kindHost", | |
| 105 | + | Summary: "host add/rm/list", | |
| 106 | + | Owner: connectivityBrokerOnly, | |
| 107 | + | DirectDialDebt: []string{"remoteHostDeps.hostStore (remote_hosts.go hostAdd/hostRm)"}, | |
| 108 | + | HostStoreWrite: true, | |
| 109 | + | Notes: "Second host-store writer today; broker is the sole writer after cutover. Host list is store-only (no dial).", | |
| 110 | + | }, | |
| 111 | + | { | |
| 112 | + | Kind: "kindKill", | |
| 113 | + | Summary: "kill one session, kill-all, explicit daemon-stop", | |
| 114 | + | Owner: connectivityBrokerOnly, | |
| 115 | + | DirectDialDebt: []string{ | |
| 116 | + | "waitForDaemonOrLifecycle realDial kill path (run.go)", | |
| 117 | + | "forceStopDaemonFallback (force_stop.go)", | |
| 118 | + | }, | |
| 119 | + | PersistMutation: true, | |
| 120 | + | Notes: "Offline runOfflineNamedKill mutation becomes daemon-owned via broker. Kill-all purges sessions and leaves the daemon running; daemon-stop is the distinct explicit stop.", | |
| 121 | + | }, | |
| 122 | + | { | |
| 123 | + | Kind: "kindCmd", | |
| 124 | + | Summary: "scriptable command requests, incl. remote-catalog", | |
| 125 | + | Owner: connectivityBrokerOnly, | |
| 126 | + | DirectDialDebt: []string{ | |
| 127 | + | "realDial command path (cmd.go)", | |
| 128 | + | "ensureDaemonWithLifecycle remote-catalog path (cmd.go)", | |
| 129 | + | }, | |
| 130 | + | Notes: "Command tracker semantics unchanged; only the carriage moves behind the broker.", | |
| 131 | + | }, | |
| 132 | + | { | |
| 133 | + | Kind: "kindUIDriver", | |
| 134 | + | Summary: "headless UI-driver attach", | |
| 135 | + | Owner: connectivityBrokerOnly, | |
| 136 | + | DirectDialDebt: []string{ | |
| 137 | + | "localDaemonDialer launch/local paths (ui_driver.go, run.go runAttachWithDeps)", | |
| 138 | + | "configuredRemoteLaunches dialerFactory (ui_driver.go)", | |
| 139 | + | }, | |
| 140 | + | Notes: "Launch-owned remote dialer and cleanup move behind the broker; UI-driver keeps no parallel carriage.", | |
| 141 | + | }, | |
| 142 | + | { | |
| 143 | + | Kind: "kindWebDaemon", | |
| 144 | + | Summary: "browser gateway launcher", | |
| 145 | + | Owner: connectivityBrokerOnly, | |
| 146 | + | DirectDialDebt: []string{"gateway session path via runAttachWithDeps (web_daemon.go)"}, | |
| 147 | + | Notes: "Launcher mechanics stay local; gateway session traffic goes through the broker like any client.", | |
| 148 | + | }, | |
| 149 | + | { | |
| 150 | + | Kind: "kindWebServe", | |
| 151 | + | Summary: "browser gateway server", | |
| 152 | + | Owner: connectivityBrokerOnly, | |
| 153 | + | DirectDialDebt: []string{"gateway session path via runAttachWithDeps (web_daemon.go)"}, | |
| 154 | + | Notes: "One client runner per WebSocket; no bypass of the broker façade.", | |
| 155 | + | }, | |
| 156 | + | { | |
| 157 | + | Kind: "kindDaemon", | |
| 158 | + | Summary: "daemon process: owns sessions, serves typed connections", | |
| 159 | + | Owner: connectivityTransportInfra, | |
| 160 | + | Notes: "Daemon accepts one typed connection per broker logical stream after cutover. Never aggregates other daemons, never owns the host registry (P7 removal set).", | |
| 161 | + | }, | |
| 162 | + | { | |
| 163 | + | Kind: "kindDaemonLauncher", | |
| 164 | + | Summary: "short-lived daemon spawn launcher", | |
| 165 | + | Owner: connectivityTransportInfra, | |
| 166 | + | Notes: "Process mechanics only; never a connectivity façade.", | |
| 167 | + | }, | |
| 168 | + | { | |
| 169 | + | Kind: "kindStdio", | |
| 170 | + | Summary: "_stdio SSH-side carriage proxy", | |
| 171 | + | Owner: connectivityTransportInfra, | |
| 172 | + | Notes: "Terminates SSH stdio transport on the far side; never resolves navigation or owns sessions.", | |
| 173 | + | }, | |
| 174 | + | { | |
| 175 | + | Kind: "kindQUICBootstrap", | |
| 176 | + | Summary: "_quic-bootstrap SSH-side bootstrap", | |
| 177 | + | Owner: connectivityTransportInfra, | |
| 178 | + | Notes: "One-time authenticated bootstrap; terminates transport, never navigation authority.", | |
| 179 | + | }, | |
| 180 | + | { | |
| 181 | + | Kind: "kindQUICProxy", | |
| 182 | + | Summary: "_quic-proxy carriage-neutral bridge", | |
| 183 | + | Owner: connectivityTransportInfra, | |
| 184 | + | Notes: "Blind proxy may forward raw envelopes; exposes no bytes to use cases and owns no sessions.", | |
| 185 | + | }, | |
| 186 | + | { | |
| 187 | + | Kind: "kindRemotePreview", | |
| 188 | + | Summary: "_remote-preview SSH-side preview carriage", | |
| 189 | + | Owner: connectivityTransportInfra, | |
| 190 | + | DirectDialDebt: []string{ | |
| 191 | + | "ipc.DialContext direct local dial (remote_preview.go)", | |
| 192 | + | }, | |
| 193 | + | Notes: "Far-side helper only. Client-facing previews flow through broker snapshots after cutover; this helper never becomes a client façade. Direct-dial debt is recorded here rather than as broker debt because the helper itself stays infra.", | |
| 194 | + | }, | |
| 195 | + | { | |
| 196 | + | Kind: "kindUIRemoteCleanup", | |
| 197 | + | Summary: "_ui-cleanup remote-side launch cleanup", | |
| 198 | + | Owner: connectivityTransportInfra, | |
| 199 | + | Notes: "Scoped remote cleanup; never launches observers or brokers recursively.", | |
| 200 | + | }, | |
| 201 | + | { | |
| 202 | + | Kind: "kindWebRenew", | |
| 203 | + | Summary: "browser token renewal", | |
| 204 | + | Owner: connectivityLocalOnly, | |
| 205 | + | Notes: "Local control only; no daemon or session connectivity.", | |
| 206 | + | }, | |
| 207 | + | { | |
| 208 | + | Kind: "kindHelp", | |
| 209 | + | Summary: "usage text", | |
| 210 | + | Owner: connectivityLocalOnly, | |
| 211 | + | Notes: "No connectivity.", | |
| 212 | + | }, | |
| 213 | + | { | |
| 214 | + | Kind: "kindVersion", | |
| 215 | + | Summary: "version line", | |
| 216 | + | Owner: connectivityLocalOnly, | |
| 217 | + | Notes: "No connectivity.", | |
| 218 | + | }, | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | // connectivityOwnerFor returns the matrix owner for a cmdKind identifier. | |
| 222 | + | func connectivityOwnerFor(kind string) (connectivityOwner, bool) { | |
| 223 | + | for _, entry := range connectivityMatrix { | |
| 224 | + | if entry.Kind == kind { | |
| 225 | + | return entry.Owner, true | |
| 226 | + | } | |
| 227 | + | } | |
| 228 | + | return 0, false | |
| 229 | + | } | |
| @@ -0,0 +1,229 @@ | |||||
| 1 | + | package app | |||
| 2 | + | ||||
| 3 | + | // Connectivity migration matrix (Plan 001 P1.2). | |||
| 4 | + | // | |||
| 5 | + | // Every CLI entry point has exactly one owner. After the P7 cutover, all | |||
| 6 | + | // ordinary user-facing operations reach daemons through the per-user | |||
| 7 | + | // connection broker; narrowly scoped daemon-facing transport helpers stay | |||
| 8 | + | // infrastructure and never become alternate client façades. | |||
| 9 | + | // | |||
| 10 | + | // Ownership rules pinned here: | |||
| 11 | + | // - Broker-only entries must not keep a direct daemon dialer or a second | |||
| 12 | + | // host-store writer after cutover. Their CurrentDebt lists today's | |||
| 13 | + | // direct-connectivity symbols to remove in the same change that | |||
| 14 | + | // activates the broker owner. | |||
| 15 | + | // - Broker never owns session persistence. Offline session mutation/list | |||
| 16 | + | // paths (PersistMutation) become daemon-owned operations reached through | |||
| 17 | + | // the broker; the persist/snapshot packages stay daemon-side. | |||
| 18 | + | // - Observation never starts a broker, never undoes an explicit | |||
| 19 | + | // daemon-stop, and never creates a session attachment. | |||
| 20 | + | // - Transport helpers terminate transport only. They never own host | |||
| 21 | + | // configuration, navigation, sessions, or picker state. | |||
| 22 | + | // | |||
| 23 | + | // The accompanying test fails when a command kind lacks a row, when a | |||
| 24 | + | // broker-only row declares no migration debt, when an infra row gains store | |||
| 25 | + | // or persistence ownership, and when direct-dial, host-store, or offline | |||
| 26 | + | // persistence symbols appear in files outside the declared debt set. | |||
| 27 | + | ||||
| 28 | + | type connectivityOwner uint8 | |||
| 29 | + | ||||
| 30 | + | const ( | |||
| 31 | + | // connectivityBrokerOnly marks ordinary user-facing operations that go | |||
| 32 | + | // through the broker façade after cutover. | |||
| 33 | + | connectivityBrokerOnly connectivityOwner = iota + 1 | |||
| 34 | + | // connectivityTransportInfra marks narrowly scoped daemon-facing | |||
| 35 | + | // transport helpers: process mechanics and carriage terminators that | |||
| 36 | + | // must never become client façades. | |||
| 37 | + | connectivityTransportInfra | |||
| 38 | + | // connectivityLocalOnly marks commands with no daemon or session | |||
| 39 | + | // connectivity at all. | |||
| 40 | + | connectivityLocalOnly | |||
| 41 | + | ) | |||
| 42 | + | ||||
| 43 | + | func (o connectivityOwner) String() string { | |||
| 44 | + | switch o { | |||
| 45 | + | case connectivityBrokerOnly: | |||
| 46 | + | return "broker-only" | |||
| 47 | + | case connectivityTransportInfra: | |||
| 48 | + | return "transport-infra" | |||
| 49 | + | case connectivityLocalOnly: | |||
| 50 | + | return "local-only" | |||
| 51 | + | default: | |||
| 52 | + | return "unknown" | |||
| 53 | + | } | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | // connectivityEntry is one row of the migration matrix. Kind is the cmdKind | |||
| 57 | + | // identifier from run.go (for example "kindAttach"). | |||
| 58 | + | type connectivityEntry struct { | |||
| 59 | + | Kind string | |||
| 60 | + | // Summary names the user-visible operation. | |||
| 61 | + | Summary string | |||
| 62 | + | Owner connectivityOwner | |||
| 63 | + | // DirectDialDebt lists today's direct-daemon dial symbols that the | |||
| 64 | + | // cutover removes for broker-only entries. Empty for infra/local rows. | |||
| 65 | + | DirectDialDebt []string | |||
| 66 | + | // HostStoreWrite reports a RemoteHostStore constructor/writer today. | |||
| 67 | + | // Only the broker owns the host store after cutover. | |||
| 68 | + | HostStoreWrite bool | |||
| 69 | + | // PersistMutation reports an offline session persistence read/write | |||
| 70 | + | // today. These become daemon-owned operations through the broker. | |||
| 71 | + | PersistMutation bool | |||
| 72 | + | // Notes records cutover direction and non-obvious boundaries. | |||
| 73 | + | Notes string | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | // connectivityMatrix classifies every cmdKind exactly once. | |||
| 77 | + | var connectivityMatrix = []connectivityEntry{ | |||
| 78 | + | { | |||
| 79 | + | Kind: "kindAttach", | |||
| 80 | + | Summary: "interactive terminal attach/new/ephemeral, incl. remote attach, detached creation, attach preflight", | |||
| 81 | + | Owner: connectivityBrokerOnly, | |||
| 82 | + | DirectDialDebt: []string{ | |||
| 83 | + | "localDaemonDialer.Dial (run.go)", | |||
| 84 | + | "dialOnlyLocalDialer.Dial inventory control (run.go)", | |||
| 85 | + | "ensureDaemonWithLifecycle detached creation (run.go createDetachedLocalSession)", | |||
| 86 | + | "registry.ResolveEndpoint remote carriage (run.go runAttachWithDeps)", | |||
| 87 | + | "listSessionsWithDialer attach preflight (attach_preflight.go sessionExists)", | |||
| 88 | + | }, | |||
| 89 | + | Notes: "vev without arguments keeps creating an ephemeral session; creation is expressed as client navigation through the broker.", | |||
| 90 | + | }, | |||
| 91 | + | { | |||
| 92 | + | Kind: "kindList", | |||
| 93 | + | Summary: "session list: local, per-host, and --all", | |||
| 94 | + | Owner: connectivityBrokerOnly, | |||
| 95 | + | DirectDialDebt: []string{ | |||
| 96 | + | "realDial listSessionsWithDialer (run.go)", | |||
| 97 | + | "waitForDaemonOrLifecycle list path (run.go)", | |||
| 98 | + | "RemoteCatalogClient.List over SSH (remote_hosts.go listAllSessions/listOneRemoteHost)", | |||
| 99 | + | }, | |||
| 100 | + | PersistMutation: true, | |||
| 101 | + | Notes: "Offline persist.LoadReadOnly list path becomes a daemon-owned operation through the broker; observation never attaches.", | |||
| 102 | + | }, | |||
| 103 | + | { | |||
| 104 | + | Kind: "kindHost", | |||
| 105 | + | Summary: "host add/rm/list", | |||
| 106 | + | Owner: connectivityBrokerOnly, | |||
| 107 | + | DirectDialDebt: []string{"remoteHostDeps.hostStore (remote_hosts.go hostAdd/hostRm)"}, | |||
| 108 | + | HostStoreWrite: true, | |||
| 109 | + | Notes: "Second host-store writer today; broker is the sole writer after cutover. Host list is store-only (no dial).", | |||
| 110 | + | }, | |||
| 111 | + | { | |||
| 112 | + | Kind: "kindKill", | |||
| 113 | + | Summary: "kill one session, kill-all, explicit daemon-stop", | |||
| 114 | + | Owner: connectivityBrokerOnly, | |||
| 115 | + | DirectDialDebt: []string{ | |||
| 116 | + | "waitForDaemonOrLifecycle realDial kill path (run.go)", | |||
| 117 | + | "forceStopDaemonFallback (force_stop.go)", | |||
| 118 | + | }, | |||
| 119 | + | PersistMutation: true, | |||
| 120 | + | Notes: "Offline runOfflineNamedKill mutation becomes daemon-owned via broker. Kill-all purges sessions and leaves the daemon running; daemon-stop is the distinct explicit stop.", | |||
| 121 | + | }, | |||
| 122 | + | { | |||
| 123 | + | Kind: "kindCmd", | |||
| 124 | + | Summary: "scriptable command requests, incl. remote-catalog", | |||
| 125 | + | Owner: connectivityBrokerOnly, | |||
| 126 | + | DirectDialDebt: []string{ | |||
| 127 | + | "realDial command path (cmd.go)", | |||
| 128 | + | "ensureDaemonWithLifecycle remote-catalog path (cmd.go)", | |||
| 129 | + | }, | |||
| 130 | + | Notes: "Command tracker semantics unchanged; only the carriage moves behind the broker.", | |||
| 131 | + | }, | |||
| 132 | + | { | |||
| 133 | + | Kind: "kindUIDriver", | |||
| 134 | + | Summary: "headless UI-driver attach", | |||
| 135 | + | Owner: connectivityBrokerOnly, | |||
| 136 | + | DirectDialDebt: []string{ | |||
| 137 | + | "localDaemonDialer launch/local paths (ui_driver.go, run.go runAttachWithDeps)", | |||
| 138 | + | "configuredRemoteLaunches dialerFactory (ui_driver.go)", | |||
| 139 | + | }, | |||
| 140 | + | Notes: "Launch-owned remote dialer and cleanup move behind the broker; UI-driver keeps no parallel carriage.", | |||
| 141 | + | }, | |||
| 142 | + | { | |||
| 143 | + | Kind: "kindWebDaemon", | |||
| 144 | + | Summary: "browser gateway launcher", | |||
| 145 | + | Owner: connectivityBrokerOnly, | |||
| 146 | + | DirectDialDebt: []string{"gateway session path via runAttachWithDeps (web_daemon.go)"}, | |||
| 147 | + | Notes: "Launcher mechanics stay local; gateway session traffic goes through the broker like any client.", | |||
| 148 | + | }, | |||
| 149 | + | { | |||
| 150 | + | Kind: "kindWebServe", | |||
| 151 | + | Summary: "browser gateway server", | |||
| 152 | + | Owner: connectivityBrokerOnly, | |||
| 153 | + | DirectDialDebt: []string{"gateway session path via runAttachWithDeps (web_daemon.go)"}, | |||
| 154 | + | Notes: "One client runner per WebSocket; no bypass of the broker façade.", | |||
| 155 | + | }, | |||
| 156 | + | { | |||
| 157 | + | Kind: "kindDaemon", | |||
| 158 | + | Summary: "daemon process: owns sessions, serves typed connections", | |||
| 159 | + | Owner: connectivityTransportInfra, | |||
| 160 | + | Notes: "Daemon accepts one typed connection per broker logical stream after cutover. Never aggregates other daemons, never owns the host registry (P7 removal set).", | |||
| 161 | + | }, | |||
| 162 | + | { | |||
| 163 | + | Kind: "kindDaemonLauncher", | |||
| 164 | + | Summary: "short-lived daemon spawn launcher", | |||
| 165 | + | Owner: connectivityTransportInfra, | |||
| 166 | + | Notes: "Process mechanics only; never a connectivity façade.", | |||
| 167 | + | }, | |||
| 168 | + | { | |||
| 169 | + | Kind: "kindStdio", | |||
| 170 | + | Summary: "_stdio SSH-side carriage proxy", | |||
| 171 | + | Owner: connectivityTransportInfra, | |||
| 172 | + | Notes: "Terminates SSH stdio transport on the far side; never resolves navigation or owns sessions.", | |||
| 173 | + | }, | |||
| 174 | + | { | |||
| 175 | + | Kind: "kindQUICBootstrap", | |||
| 176 | + | Summary: "_quic-bootstrap SSH-side bootstrap", | |||
| 177 | + | Owner: connectivityTransportInfra, | |||
| 178 | + | Notes: "One-time authenticated bootstrap; terminates transport, never navigation authority.", | |||
| 179 | + | }, | |||
| 180 | + | { | |||
| 181 | + | Kind: "kindQUICProxy", | |||
| 182 | + | Summary: "_quic-proxy carriage-neutral bridge", | |||
| 183 | + | Owner: connectivityTransportInfra, | |||
| 184 | + | Notes: "Blind proxy may forward raw envelopes; exposes no bytes to use cases and owns no sessions.", | |||
| 185 | + | }, | |||
| 186 | + | { | |||
| 187 | + | Kind: "kindRemotePreview", | |||
| 188 | + | Summary: "_remote-preview SSH-side preview carriage", | |||
| 189 | + | Owner: connectivityTransportInfra, | |||
| 190 | + | DirectDialDebt: []string{ | |||
| 191 | + | "ipc.DialContext direct local dial (remote_preview.go)", | |||
| 192 | + | }, | |||
| 193 | + | Notes: "Far-side helper only. Client-facing previews flow through broker snapshots after cutover; this helper never becomes a client façade. Direct-dial debt is recorded here rather than as broker debt because the helper itself stays infra.", | |||
| 194 | + | }, | |||
| 195 | + | { | |||
| 196 | + | Kind: "kindUIRemoteCleanup", | |||
| 197 | + | Summary: "_ui-cleanup remote-side launch cleanup", | |||
| 198 | + | Owner: connectivityTransportInfra, | |||
| 199 | + | Notes: "Scoped remote cleanup; never launches observers or brokers recursively.", | |||
| 200 | + | }, | |||
| 201 | + | { | |||
| 202 | + | Kind: "kindWebRenew", | |||
| 203 | + | Summary: "browser token renewal", | |||
| 204 | + | Owner: connectivityLocalOnly, | |||
| 205 | + | Notes: "Local control only; no daemon or session connectivity.", | |||
| 206 | + | }, | |||
| 207 | + | { | |||
| 208 | + | Kind: "kindHelp", | |||
| 209 | + | Summary: "usage text", | |||
| 210 | + | Owner: connectivityLocalOnly, | |||
| 211 | + | Notes: "No connectivity.", | |||
| 212 | + | }, | |||
| 213 | + | { | |||
| 214 | + | Kind: "kindVersion", | |||
| 215 | + | Summary: "version line", | |||
| 216 | + | Owner: connectivityLocalOnly, | |||
| 217 | + | Notes: "No connectivity.", | |||
| 218 | + | }, | |||
| 219 | + | } | |||
| 220 | + | ||||
| 221 | + | // connectivityOwnerFor returns the matrix owner for a cmdKind identifier. | |||
| 222 | + | func connectivityOwnerFor(kind string) (connectivityOwner, bool) { | |||
| 223 | + | for _, entry := range connectivityMatrix { | |||
| 224 | + | if entry.Kind == kind { | |||
| 225 | + | return entry.Owner, true | |||
| 226 | + | } | |||
| 227 | + | } | |||
| 228 | + | return 0, false | |||
| 229 | + | } | |||
A internal/app/connectivity_matrix_test.go +238 -0
| @@ -0,0 +1,238 @@ | |||
| 1 | + | package app | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "go/ast" | |
| 5 | + | "go/parser" | |
| 6 | + | "go/token" | |
| 7 | + | "os" | |
| 8 | + | "path/filepath" | |
| 9 | + | "regexp" | |
| 10 | + | "sort" | |
| 11 | + | "strings" | |
| 12 | + | "testing" | |
| 13 | + | ||
| 14 | + | "github.com/stretchr/testify/require" | |
| 15 | + | ) | |
| 16 | + | ||
| 17 | + | // TestConnectivityMatrixCoversEveryCommandKind pins P1.2: every cmdKind | |
| 18 | + | // constant declared in run.go has exactly one matrix row. A new CLI entry | |
| 19 | + | // point without a row fails here before any transport work can ignore it. | |
| 20 | + | func TestConnectivityMatrixCoversEveryCommandKind(t *testing.T) { | |
| 21 | + | t.Parallel() | |
| 22 | + | ||
| 23 | + | kinds := commandKindsFromRunGo(t) | |
| 24 | + | require.NotEmpty(t, kinds, "run.go must declare cmdKind constants") | |
| 25 | + | ||
| 26 | + | seen := make(map[string]int, len(connectivityMatrix)) | |
| 27 | + | for _, entry := range connectivityMatrix { | |
| 28 | + | require.NotEmpty(t, entry.Kind, "matrix row must name a cmdKind") | |
| 29 | + | require.NotEmpty(t, entry.Summary, "matrix row %q must summarize the operation", entry.Kind) | |
| 30 | + | require.NotEmpty(t, entry.Notes, "matrix row %q must record cutover direction", entry.Kind) | |
| 31 | + | seen[entry.Kind]++ | |
| 32 | + | } | |
| 33 | + | for kind, count := range seen { | |
| 34 | + | require.Equal(t, 1, count, "matrix row %q must appear exactly once", kind) | |
| 35 | + | } | |
| 36 | + | var missing, extra []string | |
| 37 | + | for _, kind := range kinds { | |
| 38 | + | if _, ok := seen[kind]; !ok { | |
| 39 | + | missing = append(missing, kind) | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | kindSet := make(map[string]struct{}, len(kinds)) | |
| 43 | + | for _, kind := range kinds { | |
| 44 | + | kindSet[kind] = struct{}{} | |
| 45 | + | } | |
| 46 | + | for kind := range seen { | |
| 47 | + | if _, ok := kindSet[kind]; !ok { | |
| 48 | + | extra = append(extra, kind) | |
| 49 | + | } | |
| 50 | + | } | |
| 51 | + | sort.Strings(missing) | |
| 52 | + | sort.Strings(extra) | |
| 53 | + | require.Empty(t, missing, "matrix is missing cmdKind rows") | |
| 54 | + | require.Empty(t, extra, "matrix references unknown cmdKind rows") | |
| 55 | + | ||
| 56 | + | owner, ok := connectivityOwnerFor("kindAttach") | |
| 57 | + | require.True(t, ok, "matrix lookup must resolve known kinds") | |
| 58 | + | require.Equal(t, connectivityBrokerOnly, owner) | |
| 59 | + | _, ok = connectivityOwnerFor("kindDoesNotExist") | |
| 60 | + | require.False(t, ok, "matrix lookup must reject unknown kinds") | |
| 61 | + | } | |
| 62 | + | ||
| 63 | + | // TestConnectivityMatrixOwnershipRules pins the P1.2 ownership invariants: | |
| 64 | + | // broker-only rows must declare migration debt, infra rows must never gain | |
| 65 | + | // store or persistence ownership, and local-only rows carry no debt. | |
| 66 | + | func TestConnectivityMatrixOwnershipRules(t *testing.T) { | |
| 67 | + | t.Parallel() | |
| 68 | + | ||
| 69 | + | tests := make([]struct { | |
| 70 | + | name string | |
| 71 | + | entry connectivityEntry | |
| 72 | + | }, 0, len(connectivityMatrix)) | |
| 73 | + | for _, entry := range connectivityMatrix { | |
| 74 | + | tests = append(tests, struct { | |
| 75 | + | name string | |
| 76 | + | entry connectivityEntry | |
| 77 | + | }{name: entry.Kind, entry: entry}) | |
| 78 | + | } | |
| 79 | + | for _, tt := range tests { | |
| 80 | + | tt := tt | |
| 81 | + | t.Run(tt.name, func(t *testing.T) { | |
| 82 | + | t.Parallel() | |
| 83 | + | entry := tt.entry | |
| 84 | + | switch entry.Owner { | |
| 85 | + | case connectivityBrokerOnly: | |
| 86 | + | hasDebt := len(entry.DirectDialDebt) > 0 || entry.HostStoreWrite || entry.PersistMutation | |
| 87 | + | require.True(t, hasDebt, "broker-only %q must declare migration debt (direct dial, host-store write, or persist mutation)", entry.Kind) | |
| 88 | + | case connectivityTransportInfra: | |
| 89 | + | require.False(t, entry.HostStoreWrite, "infra %q must never own the host store", entry.Kind) | |
| 90 | + | require.False(t, entry.PersistMutation, "infra %q must never own session persistence", entry.Kind) | |
| 91 | + | if len(entry.DirectDialDebt) > 0 { | |
| 92 | + | require.NotEmpty(t, entry.Notes, "infra %q with local dial debt must justify why it stays infra", entry.Kind) | |
| 93 | + | } | |
| 94 | + | case connectivityLocalOnly: | |
| 95 | + | require.Empty(t, entry.DirectDialDebt, "local-only %q must carry no dial debt", entry.Kind) | |
| 96 | + | require.False(t, entry.HostStoreWrite, "local-only %q must never own the host store", entry.Kind) | |
| 97 | + | require.False(t, entry.PersistMutation, "local-only %q must never own session persistence", entry.Kind) | |
| 98 | + | default: | |
| 99 | + | t.Fatalf("row %q has unknown owner %v", entry.Kind, entry.Owner) | |
| 100 | + | } | |
| 101 | + | for _, debt := range entry.DirectDialDebt { | |
| 102 | + | require.NotEmpty(t, debt, "row %q has an empty debt entry", entry.Kind) | |
| 103 | + | } | |
| 104 | + | }) | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | // TestConnectivityMatrixDebtFilesExist keeps the matrix honest: every file | |
| 109 | + | // named in a debt entry must exist under internal/app. | |
| 110 | + | func TestConnectivityMatrixDebtFilesExist(t *testing.T) { | |
| 111 | + | t.Parallel() | |
| 112 | + | ||
| 113 | + | fileRef := regexp.MustCompile(`([a-z_]+\.go)`) | |
| 114 | + | for _, entry := range connectivityMatrix { | |
| 115 | + | for _, debt := range entry.DirectDialDebt { | |
| 116 | + | for _, match := range fileRef.FindAllStringSubmatch(debt, -1) { | |
| 117 | + | path := filepath.Join("internal", "app", match[1]) | |
| 118 | + | // Tests run with the package dir as CWD, so resolve from there. | |
| 119 | + | if _, err := os.Stat(match[1]); err == nil { | |
| 120 | + | continue | |
| 121 | + | } | |
| 122 | + | if _, err := os.Stat(path); err == nil { | |
| 123 | + | continue | |
| 124 | + | } | |
| 125 | + | t.Errorf("row %q references missing file %q (debt %q)", entry.Kind, match[1], debt) | |
| 126 | + | } | |
| 127 | + | } | |
| 128 | + | } | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // TestConnectivityMatrixCurrentDebtPresent pins today's direct-connectivity | |
| 132 | + | // debt so the P7 cutover can find every site it must remove in the same | |
| 133 | + | // change that activates the broker owner. If any of these symbols disappear, | |
| 134 | + | // update the matrix in the same change. | |
| 135 | + | func TestConnectivityMatrixCurrentDebtPresent(t *testing.T) { | |
| 136 | + | t.Parallel() | |
| 137 | + | ||
| 138 | + | sources := loadAppSources(t, false) | |
| 139 | + | ||
| 140 | + | assertContains := func(symbol, file string) { | |
| 141 | + | t.Helper() | |
| 142 | + | body, ok := sources[file] | |
| 143 | + | require.True(t, ok, "expected app source file %q", file) | |
| 144 | + | require.Contains(t, body, symbol, "expected debt symbol %q in %q", symbol, file) | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | assertContains("type localDaemonDialer struct", "run.go") | |
| 148 | + | assertContains("type dialOnlyLocalDialer struct", "run.go") | |
| 149 | + | assertContains("func (d localDaemonDialer) Dial", "run.go") | |
| 150 | + | assertContains("func (d dialOnlyLocalDialer) Dial", "run.go") | |
| 151 | + | assertContains("func ensureDaemonWithLifecycle", "spawn.go") | |
| 152 | + | assertContains("func waitForDaemonOrLifecycle", "spawn.go") | |
| 153 | + | assertContains("func realDial", "spawn.go") | |
| 154 | + | assertContains("func realSpawn", "spawn.go") | |
| 155 | + | assertContains("func (d remoteHostDeps) hostStore", "remote_hosts.go") | |
| 156 | + | assertContains("func hostAdd", "remote_hosts.go") | |
| 157 | + | assertContains("func hostRm", "remote_hosts.go") | |
| 158 | + | assertContains("func listSessionsWithDialer", "run.go") | |
| 159 | + | assertContains("func sessionExists", "attach_preflight.go") | |
| 160 | + | assertContains("func runOfflineNamedKill", "run.go") | |
| 161 | + | assertContains("persist.LoadReadOnly", "run.go") | |
| 162 | + | assertContains("persist.OpenOrCreate", "run.go") | |
| 163 | + | assertContains("func newClientHostRegistry", "client_hosts.go") | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | // TestConnectivityMatrixSingleHostStoreWriterSet fails when a new | |
| 167 | + | // RemoteHostStore constructor call site appears outside the declared set. | |
| 168 | + | // After cutover the broker is the sole writer; until then the matrix must | |
| 169 | + | // name every writer so none is missed. | |
| 170 | + | func TestConnectivityMatrixSingleHostStoreWriterSet(t *testing.T) { | |
| 171 | + | t.Parallel() | |
| 172 | + | ||
| 173 | + | sources := loadAppSources(t, false) | |
| 174 | + | var writers []string | |
| 175 | + | for file, body := range sources { | |
| 176 | + | if strings.Contains(body, "NewFileHostStore") { | |
| 177 | + | writers = append(writers, file) | |
| 178 | + | } | |
| 179 | + | } | |
| 180 | + | sort.Strings(writers) | |
| 181 | + | require.Equal(t, []string{"remote_hosts.go", "run.go"}, writers, | |
| 182 | + | "host-store constructor sites changed; update the matrix debt set in the same change") | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | // commandKindsFromRunGo extracts the kind* const names from run.go so the | |
| 186 | + | // matrix test tracks the real dispatch table instead of a hardcoded copy. | |
| 187 | + | func commandKindsFromRunGo(t *testing.T) []string { | |
| 188 | + | t.Helper() | |
| 189 | + | ||
| 190 | + | fset := token.NewFileSet() | |
| 191 | + | file, err := parser.ParseFile(fset, "run.go", nil, 0) | |
| 192 | + | require.NoError(t, err, "parse run.go for cmdKind constants") | |
| 193 | + | ||
| 194 | + | var kinds []string | |
| 195 | + | for _, decl := range file.Decls { | |
| 196 | + | gen, ok := decl.(*ast.GenDecl) | |
| 197 | + | if !ok || gen.Tok != token.CONST { | |
| 198 | + | continue | |
| 199 | + | } | |
| 200 | + | for _, spec := range gen.Specs { | |
| 201 | + | value, ok := spec.(*ast.ValueSpec) | |
| 202 | + | if !ok { | |
| 203 | + | continue | |
| 204 | + | } | |
| 205 | + | for _, name := range value.Names { | |
| 206 | + | if strings.HasPrefix(name.Name, "kind") { | |
| 207 | + | kinds = append(kinds, name.Name) | |
| 208 | + | } | |
| 209 | + | } | |
| 210 | + | } | |
| 211 | + | } | |
| 212 | + | sort.Strings(kinds) | |
| 213 | + | return kinds | |
| 214 | + | } | |
| 215 | + | ||
| 216 | + | // loadAppSources reads production (non-test) .go files in this package. | |
| 217 | + | // When includeTests is false, _test.go files are skipped. | |
| 218 | + | func loadAppSources(t *testing.T, includeTests bool) map[string]string { | |
| 219 | + | t.Helper() | |
| 220 | + | ||
| 221 | + | entries, err := os.ReadDir(".") | |
| 222 | + | require.NoError(t, err, "read app package dir") | |
| 223 | + | sources := make(map[string]string) | |
| 224 | + | for _, entry := range entries { | |
| 225 | + | name := entry.Name() | |
| 226 | + | if entry.IsDir() || !strings.HasSuffix(name, ".go") { | |
| 227 | + | continue | |
| 228 | + | } | |
| 229 | + | if !includeTests && strings.HasSuffix(name, "_test.go") { | |
| 230 | + | continue | |
| 231 | + | } | |
| 232 | + | raw, err := os.ReadFile(name) | |
| 233 | + | require.NoError(t, err, "read app source %q", name) | |
| 234 | + | sources[name] = string(raw) | |
| 235 | + | } | |
| 236 | + | require.NotEmpty(t, sources, "app sources must not be empty") | |
| 237 | + | return sources | |
| 238 | + | } | |
| @@ -0,0 +1,238 @@ | |||||
| 1 | + | package app | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "go/ast" | |||
| 5 | + | "go/parser" | |||
| 6 | + | "go/token" | |||
| 7 | + | "os" | |||
| 8 | + | "path/filepath" | |||
| 9 | + | "regexp" | |||
| 10 | + | "sort" | |||
| 11 | + | "strings" | |||
| 12 | + | "testing" | |||
| 13 | + | ||||
| 14 | + | "github.com/stretchr/testify/require" | |||
| 15 | + | ) | |||
| 16 | + | ||||
| 17 | + | // TestConnectivityMatrixCoversEveryCommandKind pins P1.2: every cmdKind | |||
| 18 | + | // constant declared in run.go has exactly one matrix row. A new CLI entry | |||
| 19 | + | // point without a row fails here before any transport work can ignore it. | |||
| 20 | + | func TestConnectivityMatrixCoversEveryCommandKind(t *testing.T) { | |||
| 21 | + | t.Parallel() | |||
| 22 | + | ||||
| 23 | + | kinds := commandKindsFromRunGo(t) | |||
| 24 | + | require.NotEmpty(t, kinds, "run.go must declare cmdKind constants") | |||
| 25 | + | ||||
| 26 | + | seen := make(map[string]int, len(connectivityMatrix)) | |||
| 27 | + | for _, entry := range connectivityMatrix { | |||
| 28 | + | require.NotEmpty(t, entry.Kind, "matrix row must name a cmdKind") | |||
| 29 | + | require.NotEmpty(t, entry.Summary, "matrix row %q must summarize the operation", entry.Kind) | |||
| 30 | + | require.NotEmpty(t, entry.Notes, "matrix row %q must record cutover direction", entry.Kind) | |||
| 31 | + | seen[entry.Kind]++ | |||
| 32 | + | } | |||
| 33 | + | for kind, count := range seen { | |||
| 34 | + | require.Equal(t, 1, count, "matrix row %q must appear exactly once", kind) | |||
| 35 | + | } | |||
| 36 | + | var missing, extra []string | |||
| 37 | + | for _, kind := range kinds { | |||
| 38 | + | if _, ok := seen[kind]; !ok { | |||
| 39 | + | missing = append(missing, kind) | |||
| 40 | + | } | |||
| 41 | + | } | |||
| 42 | + | kindSet := make(map[string]struct{}, len(kinds)) | |||
| 43 | + | for _, kind := range kinds { | |||
| 44 | + | kindSet[kind] = struct{}{} | |||
| 45 | + | } | |||
| 46 | + | for kind := range seen { | |||
| 47 | + | if _, ok := kindSet[kind]; !ok { | |||
| 48 | + | extra = append(extra, kind) | |||
| 49 | + | } | |||
| 50 | + | } | |||
| 51 | + | sort.Strings(missing) | |||
| 52 | + | sort.Strings(extra) | |||
| 53 | + | require.Empty(t, missing, "matrix is missing cmdKind rows") | |||
| 54 | + | require.Empty(t, extra, "matrix references unknown cmdKind rows") | |||
| 55 | + | ||||
| 56 | + | owner, ok := connectivityOwnerFor("kindAttach") | |||
| 57 | + | require.True(t, ok, "matrix lookup must resolve known kinds") | |||
| 58 | + | require.Equal(t, connectivityBrokerOnly, owner) | |||
| 59 | + | _, ok = connectivityOwnerFor("kindDoesNotExist") | |||
| 60 | + | require.False(t, ok, "matrix lookup must reject unknown kinds") | |||
| 61 | + | } | |||
| 62 | + | ||||
| 63 | + | // TestConnectivityMatrixOwnershipRules pins the P1.2 ownership invariants: | |||
| 64 | + | // broker-only rows must declare migration debt, infra rows must never gain | |||
| 65 | + | // store or persistence ownership, and local-only rows carry no debt. | |||
| 66 | + | func TestConnectivityMatrixOwnershipRules(t *testing.T) { | |||
| 67 | + | t.Parallel() | |||
| 68 | + | ||||
| 69 | + | tests := make([]struct { | |||
| 70 | + | name string | |||
| 71 | + | entry connectivityEntry | |||
| 72 | + | }, 0, len(connectivityMatrix)) | |||
| 73 | + | for _, entry := range connectivityMatrix { | |||
| 74 | + | tests = append(tests, struct { | |||
| 75 | + | name string | |||
| 76 | + | entry connectivityEntry | |||
| 77 | + | }{name: entry.Kind, entry: entry}) | |||
| 78 | + | } | |||
| 79 | + | for _, tt := range tests { | |||
| 80 | + | tt := tt | |||
| 81 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 82 | + | t.Parallel() | |||
| 83 | + | entry := tt.entry | |||
| 84 | + | switch entry.Owner { | |||
| 85 | + | case connectivityBrokerOnly: | |||
| 86 | + | hasDebt := len(entry.DirectDialDebt) > 0 || entry.HostStoreWrite || entry.PersistMutation | |||
| 87 | + | require.True(t, hasDebt, "broker-only %q must declare migration debt (direct dial, host-store write, or persist mutation)", entry.Kind) | |||
| 88 | + | case connectivityTransportInfra: | |||
| 89 | + | require.False(t, entry.HostStoreWrite, "infra %q must never own the host store", entry.Kind) | |||
| 90 | + | require.False(t, entry.PersistMutation, "infra %q must never own session persistence", entry.Kind) | |||
| 91 | + | if len(entry.DirectDialDebt) > 0 { | |||
| 92 | + | require.NotEmpty(t, entry.Notes, "infra %q with local dial debt must justify why it stays infra", entry.Kind) | |||
| 93 | + | } | |||
| 94 | + | case connectivityLocalOnly: | |||
| 95 | + | require.Empty(t, entry.DirectDialDebt, "local-only %q must carry no dial debt", entry.Kind) | |||
| 96 | + | require.False(t, entry.HostStoreWrite, "local-only %q must never own the host store", entry.Kind) | |||
| 97 | + | require.False(t, entry.PersistMutation, "local-only %q must never own session persistence", entry.Kind) | |||
| 98 | + | default: | |||
| 99 | + | t.Fatalf("row %q has unknown owner %v", entry.Kind, entry.Owner) | |||
| 100 | + | } | |||
| 101 | + | for _, debt := range entry.DirectDialDebt { | |||
| 102 | + | require.NotEmpty(t, debt, "row %q has an empty debt entry", entry.Kind) | |||
| 103 | + | } | |||
| 104 | + | }) | |||
| 105 | + | } | |||
| 106 | + | } | |||
| 107 | + | ||||
| 108 | + | // TestConnectivityMatrixDebtFilesExist keeps the matrix honest: every file | |||
| 109 | + | // named in a debt entry must exist under internal/app. | |||
| 110 | + | func TestConnectivityMatrixDebtFilesExist(t *testing.T) { | |||
| 111 | + | t.Parallel() | |||
| 112 | + | ||||
| 113 | + | fileRef := regexp.MustCompile(`([a-z_]+\.go)`) | |||
| 114 | + | for _, entry := range connectivityMatrix { | |||
| 115 | + | for _, debt := range entry.DirectDialDebt { | |||
| 116 | + | for _, match := range fileRef.FindAllStringSubmatch(debt, -1) { | |||
| 117 | + | path := filepath.Join("internal", "app", match[1]) | |||
| 118 | + | // Tests run with the package dir as CWD, so resolve from there. | |||
| 119 | + | if _, err := os.Stat(match[1]); err == nil { | |||
| 120 | + | continue | |||
| 121 | + | } | |||
| 122 | + | if _, err := os.Stat(path); err == nil { | |||
| 123 | + | continue | |||
| 124 | + | } | |||
| 125 | + | t.Errorf("row %q references missing file %q (debt %q)", entry.Kind, match[1], debt) | |||
| 126 | + | } | |||
| 127 | + | } | |||
| 128 | + | } | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // TestConnectivityMatrixCurrentDebtPresent pins today's direct-connectivity | |||
| 132 | + | // debt so the P7 cutover can find every site it must remove in the same | |||
| 133 | + | // change that activates the broker owner. If any of these symbols disappear, | |||
| 134 | + | // update the matrix in the same change. | |||
| 135 | + | func TestConnectivityMatrixCurrentDebtPresent(t *testing.T) { | |||
| 136 | + | t.Parallel() | |||
| 137 | + | ||||
| 138 | + | sources := loadAppSources(t, false) | |||
| 139 | + | ||||
| 140 | + | assertContains := func(symbol, file string) { | |||
| 141 | + | t.Helper() | |||
| 142 | + | body, ok := sources[file] | |||
| 143 | + | require.True(t, ok, "expected app source file %q", file) | |||
| 144 | + | require.Contains(t, body, symbol, "expected debt symbol %q in %q", symbol, file) | |||
| 145 | + | } | |||
| 146 | + | ||||
| 147 | + | assertContains("type localDaemonDialer struct", "run.go") | |||
| 148 | + | assertContains("type dialOnlyLocalDialer struct", "run.go") | |||
| 149 | + | assertContains("func (d localDaemonDialer) Dial", "run.go") | |||
| 150 | + | assertContains("func (d dialOnlyLocalDialer) Dial", "run.go") | |||
| 151 | + | assertContains("func ensureDaemonWithLifecycle", "spawn.go") | |||
| 152 | + | assertContains("func waitForDaemonOrLifecycle", "spawn.go") | |||
| 153 | + | assertContains("func realDial", "spawn.go") | |||
| 154 | + | assertContains("func realSpawn", "spawn.go") | |||
| 155 | + | assertContains("func (d remoteHostDeps) hostStore", "remote_hosts.go") | |||
| 156 | + | assertContains("func hostAdd", "remote_hosts.go") | |||
| 157 | + | assertContains("func hostRm", "remote_hosts.go") | |||
| 158 | + | assertContains("func listSessionsWithDialer", "run.go") | |||
| 159 | + | assertContains("func sessionExists", "attach_preflight.go") | |||
| 160 | + | assertContains("func runOfflineNamedKill", "run.go") | |||
| 161 | + | assertContains("persist.LoadReadOnly", "run.go") | |||
| 162 | + | assertContains("persist.OpenOrCreate", "run.go") | |||
| 163 | + | assertContains("func newClientHostRegistry", "client_hosts.go") | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | // TestConnectivityMatrixSingleHostStoreWriterSet fails when a new | |||
| 167 | + | // RemoteHostStore constructor call site appears outside the declared set. | |||
| 168 | + | // After cutover the broker is the sole writer; until then the matrix must | |||
| 169 | + | // name every writer so none is missed. | |||
| 170 | + | func TestConnectivityMatrixSingleHostStoreWriterSet(t *testing.T) { | |||
| 171 | + | t.Parallel() | |||
| 172 | + | ||||
| 173 | + | sources := loadAppSources(t, false) | |||
| 174 | + | var writers []string | |||
| 175 | + | for file, body := range sources { | |||
| 176 | + | if strings.Contains(body, "NewFileHostStore") { | |||
| 177 | + | writers = append(writers, file) | |||
| 178 | + | } | |||
| 179 | + | } | |||
| 180 | + | sort.Strings(writers) | |||
| 181 | + | require.Equal(t, []string{"remote_hosts.go", "run.go"}, writers, | |||
| 182 | + | "host-store constructor sites changed; update the matrix debt set in the same change") | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | // commandKindsFromRunGo extracts the kind* const names from run.go so the | |||
| 186 | + | // matrix test tracks the real dispatch table instead of a hardcoded copy. | |||
| 187 | + | func commandKindsFromRunGo(t *testing.T) []string { | |||
| 188 | + | t.Helper() | |||
| 189 | + | ||||
| 190 | + | fset := token.NewFileSet() | |||
| 191 | + | file, err := parser.ParseFile(fset, "run.go", nil, 0) | |||
| 192 | + | require.NoError(t, err, "parse run.go for cmdKind constants") | |||
| 193 | + | ||||
| 194 | + | var kinds []string | |||
| 195 | + | for _, decl := range file.Decls { | |||
| 196 | + | gen, ok := decl.(*ast.GenDecl) | |||
| 197 | + | if !ok || gen.Tok != token.CONST { | |||
| 198 | + | continue | |||
| 199 | + | } | |||
| 200 | + | for _, spec := range gen.Specs { | |||
| 201 | + | value, ok := spec.(*ast.ValueSpec) | |||
| 202 | + | if !ok { | |||
| 203 | + | continue | |||
| 204 | + | } | |||
| 205 | + | for _, name := range value.Names { | |||
| 206 | + | if strings.HasPrefix(name.Name, "kind") { | |||
| 207 | + | kinds = append(kinds, name.Name) | |||
| 208 | + | } | |||
| 209 | + | } | |||
| 210 | + | } | |||
| 211 | + | } | |||
| 212 | + | sort.Strings(kinds) | |||
| 213 | + | return kinds | |||
| 214 | + | } | |||
| 215 | + | ||||
| 216 | + | // loadAppSources reads production (non-test) .go files in this package. | |||
| 217 | + | // When includeTests is false, _test.go files are skipped. | |||
| 218 | + | func loadAppSources(t *testing.T, includeTests bool) map[string]string { | |||
| 219 | + | t.Helper() | |||
| 220 | + | ||||
| 221 | + | entries, err := os.ReadDir(".") | |||
| 222 | + | require.NoError(t, err, "read app package dir") | |||
| 223 | + | sources := make(map[string]string) | |||
| 224 | + | for _, entry := range entries { | |||
| 225 | + | name := entry.Name() | |||
| 226 | + | if entry.IsDir() || !strings.HasSuffix(name, ".go") { | |||
| 227 | + | continue | |||
| 228 | + | } | |||
| 229 | + | if !includeTests && strings.HasSuffix(name, "_test.go") { | |||
| 230 | + | continue | |||
| 231 | + | } | |||
| 232 | + | raw, err := os.ReadFile(name) | |||
| 233 | + | require.NoError(t, err, "read app source %q", name) | |||
| 234 | + | sources[name] = string(raw) | |||
| 235 | + | } | |||
| 236 | + | require.NotEmpty(t, sources, "app sources must not be empty") | |||
| 237 | + | return sources | |||
| 238 | + | } | |||
A internal/ports/broker.go +641 -0
| @@ -0,0 +1,641 @@ | |||
| 1 | + | package ports | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "errors" | |
| 6 | + | "fmt" | |
| 7 | + | "strings" | |
| 8 | + | "unicode" | |
| 9 | + | "unicode/utf8" | |
| 10 | + | ||
| 11 | + | "github.com/bnema/vev/internal/domain" | |
| 12 | + | "github.com/bnema/vev/internal/protocol" | |
| 13 | + | "github.com/bnema/vev/internal/protocol/catalogue" | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // Broker semantic contracts (Plan 001 P1.3). | |
| 17 | + | // | |
| 18 | + | // The broker is the machine-wide owner of connectivity: configured hosts, | |
| 19 | + | // daemon reachability and catalogue snapshots, pooled physical transports, | |
| 20 | + | // and multiplexed logical streams. It never owns picker UI, navigation | |
| 21 | + | // decisions, sessions, PTYs, terminal geometry, or attachment semantics. | |
| 22 | + | // | |
| 23 | + | // Identity levels stay distinct: | |
| 24 | + | // - configured host registration/incarnation (domain.RemoteRegistration) | |
| 25 | + | // - authenticated daemon identity used for pooling (BrokerDaemonIdentity) | |
| 26 | + | // - daemon process incarnation used to detect restart | |
| 27 | + | // (BrokerDaemonIncarnation) | |
| 28 | + | // - exact session lifecycle identity (domain.SessionLifecycleID via | |
| 29 | + | // protocol.ExactSessionTarget) | |
| 30 | + | // - broker epoch/revision (BrokerEpoch/BrokerRevision) | |
| 31 | + | // - client connection identity (BrokerConnectionID) | |
| 32 | + | // - logical stream identity (BrokerStreamID) | |
| 33 | + | // | |
| 34 | + | // An epoch change forces a full resynchronization: stream IDs and events | |
| 35 | + | // are scoped to one client connection and broker epoch, so stale | |
| 36 | + | // completions cannot revive an attachment. Tombstones and registration | |
| 37 | + | // incarnation fence remove/re-add races independently of the epoch. | |
| 38 | + | // | |
| 39 | + | // Raw frames, codecs, environment policy enforcement, and worker | |
| 40 | + | // implementations live outside this package. Per-request session | |
| 41 | + | // environment (BrokerOpenStreamRequest.Env) stays separate from the broker | |
| 42 | + | // process environment. | |
| 43 | + | ||
| 44 | + | const ( | |
| 45 | + | // BrokerMaxHosts bounds one snapshot publication. It mirrors the | |
| 46 | + | // catalogue host budget so a broker snapshot never exceeds what | |
| 47 | + | // discovery can produce. | |
| 48 | + | BrokerMaxHosts = catalogue.RemoteCatalogMaxHosts | |
| 49 | + | // BrokerMaxSessionsPerHost bounds sessions carried per host in one | |
| 50 | + | // snapshot publication. | |
| 51 | + | BrokerMaxSessionsPerHost = catalogue.RemoteCatalogMaxSessions | |
| 52 | + | // BrokerMaxTombstones bounds retired registrations carried alongside | |
| 53 | + | // one snapshot for remove/re-add fencing. | |
| 54 | + | BrokerMaxTombstones = catalogue.RemoteCatalogMaxHosts | |
| 55 | + | // BrokerMaxErrorBytes bounds human-readable error text in broker | |
| 56 | + | // results. The underlying cause stays behind errors.Is/As where the | |
| 57 | + | // contract carries an error value. | |
| 58 | + | BrokerMaxErrorBytes = 512 | |
| 59 | + | // BrokerMaxEnvEntries bounds per-request environment entries. The | |
| 60 | + | // broker never merges these into its own process environment. | |
| 61 | + | BrokerMaxEnvEntries = 128 | |
| 62 | + | // BrokerMaxEnvEntryBytes bounds one per-request environment entry. | |
| 63 | + | BrokerMaxEnvEntryBytes = 4 << 10 | |
| 64 | + | // BrokerMaxTransportBytes bounds the opaque transport policy token. | |
| 65 | + | BrokerMaxTransportBytes = 64 | |
| 66 | + | // BrokerMaxIdentityBytes bounds the authenticated daemon identity token. | |
| 67 | + | BrokerMaxIdentityBytes = 256 | |
| 68 | + | ) | |
| 69 | + | ||
| 70 | + | // BrokerEpoch identifies one broker process incarnation. Every broker | |
| 71 | + | // process samples a fresh non-zero epoch at startup; every publication | |
| 72 | + | // carries a revision within that epoch. Clients discard all broker state | |
| 73 | + | // and resubscribe when the epoch changes. | |
| 74 | + | type BrokerEpoch uint64 | |
| 75 | + | ||
| 76 | + | // BrokerRevision is monotonic within one broker epoch. Revisions never | |
| 77 | + | // compare across epochs. | |
| 78 | + | type BrokerRevision uint64 | |
| 79 | + | ||
| 80 | + | // BrokerConnectionID identifies one client connection to the broker, | |
| 81 | + | // scoped to a broker epoch. It never identifies a session or a stream. | |
| 82 | + | type BrokerConnectionID [16]byte | |
| 83 | + | ||
| 84 | + | // BrokerStreamID identifies one logical stream within a client connection | |
| 85 | + | // and broker epoch. It never crosses connections or epochs. | |
| 86 | + | type BrokerStreamID uint64 | |
| 87 | + | ||
| 88 | + | // BrokerOperationID is the idempotency identity for one mutating broker | |
| 89 | + | // operation. Clients refresh authoritative state on outcome-unknown and | |
| 90 | + | // never blindly replay a non-idempotent operation. | |
| 91 | + | type BrokerOperationID [16]byte | |
| 92 | + | ||
| 93 | + | // BrokerDaemonIncarnation identifies one daemon process incarnation and | |
| 94 | + | // detects restarts behind a stable authenticated identity. | |
| 95 | + | type BrokerDaemonIncarnation [16]byte | |
| 96 | + | ||
| 97 | + | func (id BrokerConnectionID) IsZero() bool { return id == (BrokerConnectionID{}) } | |
| 98 | + | ||
| 99 | + | func (id BrokerConnectionID) Validate() error { | |
| 100 | + | if id.IsZero() { | |
| 101 | + | return errors.New("ports: broker connection ID is zero") | |
| 102 | + | } | |
| 103 | + | return nil | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | func (id BrokerStreamID) Validate() error { | |
| 107 | + | if id == 0 { | |
| 108 | + | return errors.New("ports: broker stream ID is zero") | |
| 109 | + | } | |
| 110 | + | return nil | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | func (id BrokerOperationID) IsZero() bool { return id == (BrokerOperationID{}) } | |
| 114 | + | ||
| 115 | + | func (id BrokerOperationID) Validate() error { | |
| 116 | + | if id.IsZero() { | |
| 117 | + | return errors.New("ports: broker operation ID is zero") | |
| 118 | + | } | |
| 119 | + | return nil | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | func (id BrokerDaemonIncarnation) IsZero() bool { return id == (BrokerDaemonIncarnation{}) } | |
| 123 | + | ||
| 124 | + | func (id BrokerDaemonIncarnation) Validate() error { | |
| 125 | + | if id.IsZero() { | |
| 126 | + | return errors.New("ports: broker daemon incarnation is zero") | |
| 127 | + | } | |
| 128 | + | return nil | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // BrokerDaemonIdentity is the authenticated daemon/service identity used | |
| 132 | + | // for physical transport pooling. Display aliases are never pooled: two | |
| 133 | + | // endpoints share a transport only after authenticated identity and every | |
| 134 | + | // trust, launch, isolation, and transport policy compare compatible. | |
| 135 | + | type BrokerDaemonIdentity string | |
| 136 | + | ||
| 137 | + | func (id BrokerDaemonIdentity) Validate() error { | |
| 138 | + | return validateBrokerToken(string(id), BrokerMaxIdentityBytes, "daemon identity") | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | // BrokerPolicy is the exact connection policy required before two | |
| 142 | + | // endpoints may share a pooled physical transport. Pooling requires exact | |
| 143 | + | // equality: conflicting policy is rejected, never merged or inherited | |
| 144 | + | // from the first launching client. | |
| 145 | + | type BrokerPolicy struct { | |
| 146 | + | ProtocolVersion uint16 | |
| 147 | + | CatalogSchemaVersion uint16 | |
| 148 | + | EnvironmentPolicy protocol.EnvironmentPolicy | |
| 149 | + | Transport string | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | func (p BrokerPolicy) Validate() error { | |
| 153 | + | if p.ProtocolVersion == 0 { | |
| 154 | + | return errors.New("ports: broker policy has no protocol version") | |
| 155 | + | } | |
| 156 | + | if p.CatalogSchemaVersion == 0 { | |
| 157 | + | return errors.New("ports: broker policy has no catalog schema version") | |
| 158 | + | } | |
| 159 | + | if err := protocol.ValidateEnvironmentPolicy(p.EnvironmentPolicy); err != nil { | |
| 160 | + | return fmt.Errorf("ports: broker policy: %w", err) | |
| 161 | + | } | |
| 162 | + | if err := validateBrokerToken(p.Transport, BrokerMaxTransportBytes, "policy transport"); err != nil { | |
| 163 | + | return err | |
| 164 | + | } | |
| 165 | + | return nil | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | // Compatible reports whether two validated policies may share a pooled | |
| 169 | + | // physical transport. It requires exact equality across every field. | |
| 170 | + | func (p BrokerPolicy) Compatible(other BrokerPolicy) bool { | |
| 171 | + | return p.ProtocolVersion == other.ProtocolVersion && | |
| 172 | + | p.CatalogSchemaVersion == other.CatalogSchemaVersion && | |
| 173 | + | p.EnvironmentPolicy == other.EnvironmentPolicy && | |
| 174 | + | p.Transport == other.Transport | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | // BrokerHostTombstone fences a removed host registration so a stale probe, | |
| 178 | + | // snapshot, or completion bound to the retired incarnation cannot overwrite | |
| 179 | + | // or revive current authority. A re-added endpoint carries a new | |
| 180 | + | // incarnation and is never fenced by an older tombstone. | |
| 181 | + | type BrokerHostTombstone struct { | |
| 182 | + | Endpoint string | |
| 183 | + | Registration domain.RemoteRegistration | |
| 184 | + | RetiredRevision BrokerRevision | |
| 185 | + | } | |
| 186 | + | ||
| 187 | + | func (t BrokerHostTombstone) Validate() error { | |
| 188 | + | if err := domain.ValidateRemoteHostTarget(t.Endpoint); err != nil { | |
| 189 | + | return fmt.Errorf("ports: broker tombstone: %w", err) | |
| 190 | + | } | |
| 191 | + | if err := t.Registration.Validate(); err != nil { | |
| 192 | + | return fmt.Errorf("ports: broker tombstone: %w", err) | |
| 193 | + | } | |
| 194 | + | if t.Endpoint != t.Registration.Endpoint { | |
| 195 | + | return errors.New("ports: broker tombstone endpoint does not match registration") | |
| 196 | + | } | |
| 197 | + | if t.RetiredRevision == 0 { | |
| 198 | + | return errors.New("ports: broker tombstone has no retired revision") | |
| 199 | + | } | |
| 200 | + | return nil | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | // Fences reports whether this tombstone retires the supplied registration: | |
| 204 | + | // same endpoint and incarnation, with a retired generation at or past the | |
| 205 | + | // candidate. A re-added endpoint with a fresh incarnation is never fenced. | |
| 206 | + | func (t BrokerHostTombstone) Fences(candidate domain.RemoteRegistration) bool { | |
| 207 | + | return t.Endpoint == candidate.Endpoint && | |
| 208 | + | t.Registration.Incarnation == candidate.Incarnation && | |
| 209 | + | t.Registration.Generation >= candidate.Generation | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | // BrokerSnapshot is an immutable, fully defensive broker publication. Hosts | |
| 213 | + | // reuse the ordered RemoteHostSnapshot projection; Removed carries the | |
| 214 | + | // bounded tombstone set that fences stale authority. Nested slices are | |
| 215 | + | // never mutated after publication. | |
| 216 | + | type BrokerSnapshot struct { | |
| 217 | + | Epoch BrokerEpoch | |
| 218 | + | Revision BrokerRevision | |
| 219 | + | Hosts []RemoteHostSnapshot | |
| 220 | + | Removed []BrokerHostTombstone | |
| 221 | + | } | |
| 222 | + | ||
| 223 | + | func (s BrokerSnapshot) Validate() error { | |
| 224 | + | if s.Epoch == 0 { | |
| 225 | + | return errors.New("ports: broker snapshot has no epoch") | |
| 226 | + | } | |
| 227 | + | if s.Revision == 0 { | |
| 228 | + | return errors.New("ports: broker snapshot has no revision") | |
| 229 | + | } | |
| 230 | + | if len(s.Hosts) > BrokerMaxHosts { | |
| 231 | + | return errors.New("ports: broker snapshot has too many hosts") | |
| 232 | + | } | |
| 233 | + | if len(s.Removed) > BrokerMaxTombstones { | |
| 234 | + | return errors.New("ports: broker snapshot has too many tombstones") | |
| 235 | + | } | |
| 236 | + | seen := make(map[string]struct{}, len(s.Hosts)) | |
| 237 | + | for _, host := range s.Hosts { | |
| 238 | + | if err := domain.ValidateRemoteHostTarget(host.Endpoint); err != nil { | |
| 239 | + | return fmt.Errorf("ports: broker snapshot: %w", err) | |
| 240 | + | } | |
| 241 | + | if err := host.Registration.Validate(); err != nil { | |
| 242 | + | return fmt.Errorf("ports: broker snapshot: %w", err) | |
| 243 | + | } | |
| 244 | + | if host.Endpoint != host.Registration.Endpoint { | |
| 245 | + | return errors.New("ports: broker snapshot host endpoint does not match registration") | |
| 246 | + | } | |
| 247 | + | if len(host.Sessions) > BrokerMaxSessionsPerHost { | |
| 248 | + | return errors.New("ports: broker snapshot host has too many sessions") | |
| 249 | + | } | |
| 250 | + | if _, dup := seen[host.Endpoint]; dup { | |
| 251 | + | return fmt.Errorf("ports: broker snapshot has duplicate host %q", host.Endpoint) | |
| 252 | + | } | |
| 253 | + | seen[host.Endpoint] = struct{}{} | |
| 254 | + | } | |
| 255 | + | for _, tombstone := range s.Removed { | |
| 256 | + | if err := tombstone.Validate(); err != nil { | |
| 257 | + | return err | |
| 258 | + | } | |
| 259 | + | if _, live := seen[tombstone.Endpoint]; live { | |
| 260 | + | return fmt.Errorf("ports: broker snapshot carries live host %q as tombstone", tombstone.Endpoint) | |
| 261 | + | } | |
| 262 | + | } | |
| 263 | + | return nil | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | // Clone returns a defensive copy with independent host, session, tab, and | |
| 267 | + | // tombstone slices. | |
| 268 | + | func (s BrokerSnapshot) Clone() BrokerSnapshot { | |
| 269 | + | out := s | |
| 270 | + | out.Hosts = make([]RemoteHostSnapshot, len(s.Hosts)) | |
| 271 | + | for i, host := range s.Hosts { | |
| 272 | + | out.Hosts[i] = host.Clone() | |
| 273 | + | } | |
| 274 | + | out.Removed = append([]BrokerHostTombstone(nil), s.Removed...) | |
| 275 | + | return out | |
| 276 | + | } | |
| 277 | + | ||
| 278 | + | // Find returns the snapshot for an exact endpoint identity. | |
| 279 | + | func (s BrokerSnapshot) Find(endpoint string) (RemoteHostSnapshot, bool) { | |
| 280 | + | for _, host := range s.Hosts { | |
| 281 | + | if host.Endpoint == endpoint { | |
| 282 | + | return host, true | |
| 283 | + | } | |
| 284 | + | } | |
| 285 | + | return RemoteHostSnapshot{}, false | |
| 286 | + | } | |
| 287 | + | ||
| 288 | + | // Supersedes reports whether s is newer than other. Any epoch change | |
| 289 | + | // forces a full resynchronization: a snapshot with a valid epoch | |
| 290 | + | // supersedes every snapshot from another epoch (or none), regardless of | |
| 291 | + | // numeric order, because epochs are fresh per broker process and never | |
| 292 | + | // ordered across processes. Within one epoch the higher revision wins. | |
| 293 | + | // Revisions never compare across epochs. A zero-epoch snapshot is | |
| 294 | + | // invalid and never supersedes. | |
| 295 | + | func (s BrokerSnapshot) Supersedes(other BrokerSnapshot) bool { | |
| 296 | + | if s.Epoch == 0 { | |
| 297 | + | return false | |
| 298 | + | } | |
| 299 | + | if s.Epoch != other.Epoch { | |
| 300 | + | return true | |
| 301 | + | } | |
| 302 | + | return s.Revision > other.Revision | |
| 303 | + | } | |
| 304 | + | ||
| 305 | + | // BrokerCompletionIsStale reports whether a probe or operation completion | |
| 306 | + | // must be rejected: a different broker epoch, or a registration that no | |
| 307 | + | // longer exactly matches current authority (endpoint, incarnation, and | |
| 308 | + | // generation must all match). | |
| 309 | + | func BrokerCompletionIsStale(currentRegistration domain.RemoteRegistration, currentEpoch BrokerEpoch, completionRegistration domain.RemoteRegistration, completionEpoch BrokerEpoch) bool { | |
| 310 | + | if currentEpoch == 0 || completionEpoch == 0 { | |
| 311 | + | return true | |
| 312 | + | } | |
| 313 | + | if currentEpoch != completionEpoch { | |
| 314 | + | return true | |
| 315 | + | } | |
| 316 | + | return !currentRegistration.Equal(completionRegistration) | |
| 317 | + | } | |
| 318 | + | ||
| 319 | + | // BrokerOpenStreamRequest asks the broker to open one independently | |
| 320 | + | // cancellable logical stream to the owning daemon. The daemon revalidates | |
| 321 | + | // the exact session identity before attachment. Env is the per-request | |
| 322 | + | // session environment; it is never inherited from the broker process | |
| 323 | + | // environment. | |
| 324 | + | type BrokerOpenStreamRequest struct { | |
| 325 | + | Connection BrokerConnectionID | |
| 326 | + | Stream BrokerStreamID | |
| 327 | + | Endpoint string | |
| 328 | + | Registration domain.RemoteRegistration | |
| 329 | + | Target protocol.ExactSessionTarget | |
| 330 | + | Env []string | |
| 331 | + | Policy BrokerPolicy | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | func (r BrokerOpenStreamRequest) Validate() error { | |
| 335 | + | if err := r.Connection.Validate(); err != nil { | |
| 336 | + | return err | |
| 337 | + | } | |
| 338 | + | if err := r.Stream.Validate(); err != nil { | |
| 339 | + | return err | |
| 340 | + | } | |
| 341 | + | if err := domain.ValidateRemoteHostTarget(r.Endpoint); err != nil { | |
| 342 | + | return fmt.Errorf("ports: broker open stream: %w", err) | |
| 343 | + | } | |
| 344 | + | if err := r.Registration.Validate(); err != nil { | |
| 345 | + | return fmt.Errorf("ports: broker open stream: %w", err) | |
| 346 | + | } | |
| 347 | + | if r.Endpoint != r.Registration.Endpoint { | |
| 348 | + | return errors.New("ports: broker open stream endpoint does not match registration") | |
| 349 | + | } | |
| 350 | + | if err := r.Target.Validate(); err != nil { | |
| 351 | + | return fmt.Errorf("ports: broker open stream: %w", err) | |
| 352 | + | } | |
| 353 | + | if uint64(len(r.Env)) > BrokerMaxEnvEntries { | |
| 354 | + | return errors.New("ports: broker open stream has too many environment entries") | |
| 355 | + | } | |
| 356 | + | for _, entry := range r.Env { | |
| 357 | + | if len(entry) > BrokerMaxEnvEntryBytes { | |
| 358 | + | return errors.New("ports: broker open stream environment entry too long") | |
| 359 | + | } | |
| 360 | + | if !utf8.ValidString(entry) { | |
| 361 | + | return errors.New("ports: broker open stream environment is not valid UTF-8") | |
| 362 | + | } | |
| 363 | + | } | |
| 364 | + | if err := r.Policy.Validate(); err != nil { | |
| 365 | + | return err | |
| 366 | + | } | |
| 367 | + | return nil | |
| 368 | + | } | |
| 369 | + | ||
| 370 | + | // BrokerMutationOutcome is the closed outcome taxonomy for a mutating | |
| 371 | + | // broker operation. OutcomeUnknown means connectivity was lost after the | |
| 372 | + | // mutation may have committed but before its result arrived: the client | |
| 373 | + | // refreshes authoritative state and never blindly replays a | |
| 374 | + | // non-idempotent operation. | |
| 375 | + | type BrokerMutationOutcome uint8 | |
| 376 | + | ||
| 377 | + | const ( | |
| 378 | + | BrokerOutcomeOK BrokerMutationOutcome = iota + 1 | |
| 379 | + | BrokerOutcomeFailed | |
| 380 | + | BrokerOutcomeUnknown | |
| 381 | + | ) | |
| 382 | + | ||
| 383 | + | func (o BrokerMutationOutcome) String() string { | |
| 384 | + | switch o { | |
| 385 | + | case BrokerOutcomeOK: | |
| 386 | + | return "ok" | |
| 387 | + | case BrokerOutcomeFailed: | |
| 388 | + | return "failed" | |
| 389 | + | case BrokerOutcomeUnknown: | |
| 390 | + | return "outcome_unknown" | |
| 391 | + | default: | |
| 392 | + | return "unknown" | |
| 393 | + | } | |
| 394 | + | } | |
| 395 | + | ||
| 396 | + | func (o BrokerMutationOutcome) Validate() error { | |
| 397 | + | switch o { | |
| 398 | + | case BrokerOutcomeOK, BrokerOutcomeFailed, BrokerOutcomeUnknown: | |
| 399 | + | return nil | |
| 400 | + | default: | |
| 401 | + | return errors.New("ports: invalid broker mutation outcome") | |
| 402 | + | } | |
| 403 | + | } | |
| 404 | + | ||
| 405 | + | // BrokerOperationResult is exactly one completion for one admitted mutating | |
| 406 | + | // operation. Text is bounded and presentation-safe; the outcome decides | |
| 407 | + | // whether the client may retry, must refresh, or must not replay. | |
| 408 | + | type BrokerOperationResult struct { | |
| 409 | + | Operation BrokerOperationID | |
| 410 | + | Outcome BrokerMutationOutcome | |
| 411 | + | Text string | |
| 412 | + | } | |
| 413 | + | ||
| 414 | + | func (r BrokerOperationResult) Validate() error { | |
| 415 | + | if err := r.Operation.Validate(); err != nil { | |
| 416 | + | return err | |
| 417 | + | } | |
| 418 | + | if err := r.Outcome.Validate(); err != nil { | |
| 419 | + | return err | |
| 420 | + | } | |
| 421 | + | if err := validateBrokerDisplayText(r.Text, BrokerMaxErrorBytes, "operation result text"); err != nil { | |
| 422 | + | return err | |
| 423 | + | } | |
| 424 | + | if r.Outcome == BrokerOutcomeOK && r.Text != "" { | |
| 425 | + | return errors.New("ports: successful broker operation carries no text") | |
| 426 | + | } | |
| 427 | + | return nil | |
| 428 | + | } | |
| 429 | + | ||
| 430 | + | // BrokerErrorCode is the closed taxonomy for broker and stream lifecycle | |
| 431 | + | // failures surfaced to clients. Every failure returns the client to the | |
| 432 | + | // picker in the same process with a visible typed error; none terminates | |
| 433 | + | // the client except an explicit exit or a fatal terminal failure. | |
| 434 | + | type BrokerErrorCode uint8 | |
| 435 | + | ||
| 436 | + | const ( | |
| 437 | + | BrokerErrorUnavailable BrokerErrorCode = iota + 1 | |
| 438 | + | BrokerErrorIncompatible | |
| 439 | + | BrokerErrorTimeout | |
| 440 | + | BrokerErrorCancelled | |
| 441 | + | BrokerErrorOutcomeUnknown | |
| 442 | + | BrokerErrorAttachmentLost | |
| 443 | + | BrokerErrorStaleEpoch | |
| 444 | + | BrokerErrorConflictingPolicy | |
| 445 | + | BrokerErrorExplicitExit | |
| 446 | + | BrokerErrorFatalTerminal | |
| 447 | + | ) | |
| 448 | + | ||
| 449 | + | func (c BrokerErrorCode) String() string { | |
| 450 | + | switch c { | |
| 451 | + | case BrokerErrorUnavailable: | |
| 452 | + | return "unavailable" | |
| 453 | + | case BrokerErrorIncompatible: | |
| 454 | + | return "incompatible" | |
| 455 | + | case BrokerErrorTimeout: | |
| 456 | + | return "timeout" | |
| 457 | + | case BrokerErrorCancelled: | |
| 458 | + | return "cancelled" | |
| 459 | + | case BrokerErrorOutcomeUnknown: | |
| 460 | + | return "outcome_unknown" | |
| 461 | + | case BrokerErrorAttachmentLost: | |
| 462 | + | return "attachment_lost" | |
| 463 | + | case BrokerErrorStaleEpoch: | |
| 464 | + | return "stale_epoch" | |
| 465 | + | case BrokerErrorConflictingPolicy: | |
| 466 | + | return "conflicting_policy" | |
| 467 | + | case BrokerErrorExplicitExit: | |
| 468 | + | return "explicit_exit" | |
| 469 | + | case BrokerErrorFatalTerminal: | |
| 470 | + | return "fatal_terminal" | |
| 471 | + | default: | |
| 472 | + | return "unknown" | |
| 473 | + | } | |
| 474 | + | } | |
| 475 | + | ||
| 476 | + | func (c BrokerErrorCode) Validate() error { | |
| 477 | + | switch c { | |
| 478 | + | case BrokerErrorUnavailable, BrokerErrorIncompatible, BrokerErrorTimeout, | |
| 479 | + | BrokerErrorCancelled, BrokerErrorOutcomeUnknown, BrokerErrorAttachmentLost, | |
| 480 | + | BrokerErrorStaleEpoch, BrokerErrorConflictingPolicy, | |
| 481 | + | BrokerErrorExplicitExit, BrokerErrorFatalTerminal: | |
| 482 | + | return nil | |
| 483 | + | default: | |
| 484 | + | return errors.New("ports: invalid broker error code") | |
| 485 | + | } | |
| 486 | + | } | |
| 487 | + | ||
| 488 | + | // BrokerError is the typed lifecycle failure carried to clients. Terminal | |
| 489 | + | // reports whether the client process itself must exit: only an explicit | |
| 490 | + | // exit or a fatal terminal failure is terminal; every other code returns | |
| 491 | + | // to the picker with a visible notification. | |
| 492 | + | type BrokerError struct { | |
| 493 | + | Code BrokerErrorCode | |
| 494 | + | Text string | |
| 495 | + | } | |
| 496 | + | ||
| 497 | + | func (e BrokerError) Error() string { | |
| 498 | + | if e.Text == "" { | |
| 499 | + | return "vev: broker " + e.Code.String() | |
| 500 | + | } | |
| 501 | + | return "vev: broker " + e.Code.String() + ": " + e.Text | |
| 502 | + | } | |
| 503 | + | ||
| 504 | + | func (e BrokerError) Validate() error { | |
| 505 | + | if err := e.Code.Validate(); err != nil { | |
| 506 | + | return err | |
| 507 | + | } | |
| 508 | + | return validateBrokerDisplayText(e.Text, BrokerMaxErrorBytes, "error text") | |
| 509 | + | } | |
| 510 | + | ||
| 511 | + | // Terminal reports whether this failure ends the client process. | |
| 512 | + | func (e BrokerError) Terminal() bool { | |
| 513 | + | return e.Code == BrokerErrorExplicitExit || e.Code == BrokerErrorFatalTerminal | |
| 514 | + | } | |
| 515 | + | ||
| 516 | + | // BrokerStreamLost is the independent typed loss event delivered per | |
| 517 | + | // affected logical stream when its physical connection fails. One stream | |
| 518 | + | // failure never closes unrelated streams; a physical loss fans out one | |
| 519 | + | // event per stream. | |
| 520 | + | type BrokerStreamLost struct { | |
| 521 | + | Connection BrokerConnectionID | |
| 522 | + | Stream BrokerStreamID | |
| 523 | + | Epoch BrokerEpoch | |
| 524 | + | Cause domain.RemoteFailureKind | |
| 525 | + | } | |
| 526 | + | ||
| 527 | + | func (e BrokerStreamLost) Validate() error { | |
| 528 | + | if err := e.Connection.Validate(); err != nil { | |
| 529 | + | return err | |
| 530 | + | } | |
| 531 | + | if err := e.Stream.Validate(); err != nil { | |
| 532 | + | return err | |
| 533 | + | } | |
| 534 | + | if e.Epoch == 0 { | |
| 535 | + | return errors.New("ports: broker stream loss has no epoch") | |
| 536 | + | } | |
| 537 | + | return nil | |
| 538 | + | } | |
| 539 | + | ||
| 540 | + | // BrokerSubscription wakes one subscriber when a newer snapshot is | |
| 541 | + | // available. Each subscriber owns a capacity-one channel and re-reads the | |
| 542 | + | // newest snapshot on wake; channels are never shared between subscribers. | |
| 543 | + | type BrokerSubscription interface { | |
| 544 | + | Changed() <-chan struct{} | |
| 545 | + | Close() | |
| 546 | + | } | |
| 547 | + | ||
| 548 | + | // BrokerService is the client-facing broker façade. All clients reach | |
| 549 | + | // local and remote daemons through it, including the local daemon. | |
| 550 | + | // Snapshot access performs no I/O; request methods are non-blocking | |
| 551 | + | // coalesced hints except where the context explicitly bounds them. | |
| 552 | + | type BrokerService interface { | |
| 553 | + | // ConnectionID returns the ID assigned when this client connection was | |
| 554 | + | // accepted. Clients carry it on stream operations to fence stale requests. | |
| 555 | + | ConnectionID() BrokerConnectionID | |
| 556 | + | Snapshot() BrokerSnapshot | |
| 557 | + | Subscribe() (BrokerSubscription, error) | |
| 558 | + | OpenStream(ctx context.Context, request BrokerOpenStreamRequest) (ClientConnection, error) | |
| 559 | + | CloseStream(connection BrokerConnectionID, stream BrokerStreamID) error | |
| 560 | + | AddHost(ctx context.Context, target string) error | |
| 561 | + | RemoveHost(ctx context.Context, target string) (bool, error) | |
| 562 | + | RequestReconcile(endpoint string) | |
| 563 | + | Close() error | |
| 564 | + | } | |
| 565 | + | ||
| 566 | + | // BrokerSnapshotStore persists the durable broker snapshot independently | |
| 567 | + | // from session state. The broker is its sole writer; session persistence | |
| 568 | + | // stays daemon-owned and is never moved here. | |
| 569 | + | type BrokerSnapshotStore interface { | |
| 570 | + | Load() (BrokerSnapshot, error) | |
| 571 | + | Store(BrokerSnapshot) error | |
| 572 | + | } | |
| 573 | + | ||
| 574 | + | // BrokerPhysicalConnection is one pooled physical transport to an | |
| 575 | + | // authenticated daemon identity. It opens one independently cancellable | |
| 576 | + | // typed session connection per logical stream and never shares stream | |
| 577 | + | // state, identity, geometry, input, output, cancellation, or application | |
| 578 | + | // errors between streams. | |
| 579 | + | type BrokerPhysicalConnection interface { | |
| 580 | + | Identity() BrokerDaemonIdentity | |
| 581 | + | Incarnation() BrokerDaemonIncarnation | |
| 582 | + | Policy() BrokerPolicy | |
| 583 | + | OpenStream(ctx context.Context, request BrokerOpenStreamRequest) (ClientConnection, error) | |
| 584 | + | Close() error | |
| 585 | + | } | |
| 586 | + | ||
| 587 | + | // BrokerEndpointConnector establishes the pooled physical transport for | |
| 588 | + | // one endpoint under an exact compatible policy. Conflicting policy is | |
| 589 | + | // rejected; the first launching client never becomes policy authority. | |
| 590 | + | type BrokerEndpointConnector interface { | |
| 591 | + | Connect(ctx context.Context, endpoint string, policy BrokerPolicy) (BrokerPhysicalConnection, error) | |
| 592 | + | } | |
| 593 | + | ||
| 594 | + | // BrokerListener accepts client connections to the per-user broker | |
| 595 | + | // endpoint. Each Accept returns a BrokerService bound to exactly one | |
| 596 | + | // accepted client connection: the implementation assigns that | |
| 597 | + | // connection's BrokerConnectionID at accept, ends the connection on | |
| 598 | + | // Close, and rejects requests carrying a mismatched Connection as stale. | |
| 599 | + | // Implementations own the owner-only directory/socket, peer credential | |
| 600 | + | // checks, bounded clients and queues, and cancellation-safe accept loops. | |
| 601 | + | type BrokerListener interface { | |
| 602 | + | Accept() (BrokerService, error) | |
| 603 | + | Close() error | |
| 604 | + | Addr() string | |
| 605 | + | } | |
| 606 | + | ||
| 607 | + | func validateBrokerToken(value string, maxBytes int, label string) error { | |
| 608 | + | if value == "" { | |
| 609 | + | return fmt.Errorf("ports: broker %s is empty", label) | |
| 610 | + | } | |
| 611 | + | if len(value) > maxBytes { | |
| 612 | + | return fmt.Errorf("ports: broker %s too long", label) | |
| 613 | + | } | |
| 614 | + | if !utf8.ValidString(value) { | |
| 615 | + | return fmt.Errorf("ports: broker %s is not valid UTF-8", label) | |
| 616 | + | } | |
| 617 | + | if strings.TrimSpace(value) != value { | |
| 618 | + | return fmt.Errorf("ports: broker %s has surrounding whitespace", label) | |
| 619 | + | } | |
| 620 | + | for _, r := range value { | |
| 621 | + | if unicode.IsControl(r) || unicode.IsSpace(r) || unicode.Is(unicode.Bidi_Control, r) || r == '\u2028' || r == '\u2029' { | |
| 622 | + | return fmt.Errorf("ports: broker %s contains disallowed characters", label) | |
| 623 | + | } | |
| 624 | + | } | |
| 625 | + | return nil | |
| 626 | + | } | |
| 627 | + | ||
| 628 | + | func validateBrokerDisplayText(value string, maxBytes int, label string) error { | |
| 629 | + | if len(value) > maxBytes { | |
| 630 | + | return fmt.Errorf("ports: broker %s too long", label) | |
| 631 | + | } | |
| 632 | + | if !utf8.ValidString(value) { | |
| 633 | + | return fmt.Errorf("ports: broker %s is not valid UTF-8", label) | |
| 634 | + | } | |
| 635 | + | for _, r := range value { | |
| 636 | + | if unicode.IsControl(r) || unicode.Is(unicode.Bidi_Control, r) || r == '\u2028' || r == '\u2029' { | |
| 637 | + | return fmt.Errorf("ports: broker %s contains disallowed characters", label) | |
| 638 | + | } | |
| 639 | + | } | |
| 640 | + | return nil | |
| 641 | + | } | |
| @@ -0,0 +1,641 @@ | |||||
| 1 | + | package ports | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "errors" | |||
| 6 | + | "fmt" | |||
| 7 | + | "strings" | |||
| 8 | + | "unicode" | |||
| 9 | + | "unicode/utf8" | |||
| 10 | + | ||||
| 11 | + | "github.com/bnema/vev/internal/domain" | |||
| 12 | + | "github.com/bnema/vev/internal/protocol" | |||
| 13 | + | "github.com/bnema/vev/internal/protocol/catalogue" | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // Broker semantic contracts (Plan 001 P1.3). | |||
| 17 | + | // | |||
| 18 | + | // The broker is the machine-wide owner of connectivity: configured hosts, | |||
| 19 | + | // daemon reachability and catalogue snapshots, pooled physical transports, | |||
| 20 | + | // and multiplexed logical streams. It never owns picker UI, navigation | |||
| 21 | + | // decisions, sessions, PTYs, terminal geometry, or attachment semantics. | |||
| 22 | + | // | |||
| 23 | + | // Identity levels stay distinct: | |||
| 24 | + | // - configured host registration/incarnation (domain.RemoteRegistration) | |||
| 25 | + | // - authenticated daemon identity used for pooling (BrokerDaemonIdentity) | |||
| 26 | + | // - daemon process incarnation used to detect restart | |||
| 27 | + | // (BrokerDaemonIncarnation) | |||
| 28 | + | // - exact session lifecycle identity (domain.SessionLifecycleID via | |||
| 29 | + | // protocol.ExactSessionTarget) | |||
| 30 | + | // - broker epoch/revision (BrokerEpoch/BrokerRevision) | |||
| 31 | + | // - client connection identity (BrokerConnectionID) | |||
| 32 | + | // - logical stream identity (BrokerStreamID) | |||
| 33 | + | // | |||
| 34 | + | // An epoch change forces a full resynchronization: stream IDs and events | |||
| 35 | + | // are scoped to one client connection and broker epoch, so stale | |||
| 36 | + | // completions cannot revive an attachment. Tombstones and registration | |||
| 37 | + | // incarnation fence remove/re-add races independently of the epoch. | |||
| 38 | + | // | |||
| 39 | + | // Raw frames, codecs, environment policy enforcement, and worker | |||
| 40 | + | // implementations live outside this package. Per-request session | |||
| 41 | + | // environment (BrokerOpenStreamRequest.Env) stays separate from the broker | |||
| 42 | + | // process environment. | |||
| 43 | + | ||||
| 44 | + | const ( | |||
| 45 | + | // BrokerMaxHosts bounds one snapshot publication. It mirrors the | |||
| 46 | + | // catalogue host budget so a broker snapshot never exceeds what | |||
| 47 | + | // discovery can produce. | |||
| 48 | + | BrokerMaxHosts = catalogue.RemoteCatalogMaxHosts | |||
| 49 | + | // BrokerMaxSessionsPerHost bounds sessions carried per host in one | |||
| 50 | + | // snapshot publication. | |||
| 51 | + | BrokerMaxSessionsPerHost = catalogue.RemoteCatalogMaxSessions | |||
| 52 | + | // BrokerMaxTombstones bounds retired registrations carried alongside | |||
| 53 | + | // one snapshot for remove/re-add fencing. | |||
| 54 | + | BrokerMaxTombstones = catalogue.RemoteCatalogMaxHosts | |||
| 55 | + | // BrokerMaxErrorBytes bounds human-readable error text in broker | |||
| 56 | + | // results. The underlying cause stays behind errors.Is/As where the | |||
| 57 | + | // contract carries an error value. | |||
| 58 | + | BrokerMaxErrorBytes = 512 | |||
| 59 | + | // BrokerMaxEnvEntries bounds per-request environment entries. The | |||
| 60 | + | // broker never merges these into its own process environment. | |||
| 61 | + | BrokerMaxEnvEntries = 128 | |||
| 62 | + | // BrokerMaxEnvEntryBytes bounds one per-request environment entry. | |||
| 63 | + | BrokerMaxEnvEntryBytes = 4 << 10 | |||
| 64 | + | // BrokerMaxTransportBytes bounds the opaque transport policy token. | |||
| 65 | + | BrokerMaxTransportBytes = 64 | |||
| 66 | + | // BrokerMaxIdentityBytes bounds the authenticated daemon identity token. | |||
| 67 | + | BrokerMaxIdentityBytes = 256 | |||
| 68 | + | ) | |||
| 69 | + | ||||
| 70 | + | // BrokerEpoch identifies one broker process incarnation. Every broker | |||
| 71 | + | // process samples a fresh non-zero epoch at startup; every publication | |||
| 72 | + | // carries a revision within that epoch. Clients discard all broker state | |||
| 73 | + | // and resubscribe when the epoch changes. | |||
| 74 | + | type BrokerEpoch uint64 | |||
| 75 | + | ||||
| 76 | + | // BrokerRevision is monotonic within one broker epoch. Revisions never | |||
| 77 | + | // compare across epochs. | |||
| 78 | + | type BrokerRevision uint64 | |||
| 79 | + | ||||
| 80 | + | // BrokerConnectionID identifies one client connection to the broker, | |||
| 81 | + | // scoped to a broker epoch. It never identifies a session or a stream. | |||
| 82 | + | type BrokerConnectionID [16]byte | |||
| 83 | + | ||||
| 84 | + | // BrokerStreamID identifies one logical stream within a client connection | |||
| 85 | + | // and broker epoch. It never crosses connections or epochs. | |||
| 86 | + | type BrokerStreamID uint64 | |||
| 87 | + | ||||
| 88 | + | // BrokerOperationID is the idempotency identity for one mutating broker | |||
| 89 | + | // operation. Clients refresh authoritative state on outcome-unknown and | |||
| 90 | + | // never blindly replay a non-idempotent operation. | |||
| 91 | + | type BrokerOperationID [16]byte | |||
| 92 | + | ||||
| 93 | + | // BrokerDaemonIncarnation identifies one daemon process incarnation and | |||
| 94 | + | // detects restarts behind a stable authenticated identity. | |||
| 95 | + | type BrokerDaemonIncarnation [16]byte | |||
| 96 | + | ||||
| 97 | + | func (id BrokerConnectionID) IsZero() bool { return id == (BrokerConnectionID{}) } | |||
| 98 | + | ||||
| 99 | + | func (id BrokerConnectionID) Validate() error { | |||
| 100 | + | if id.IsZero() { | |||
| 101 | + | return errors.New("ports: broker connection ID is zero") | |||
| 102 | + | } | |||
| 103 | + | return nil | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | func (id BrokerStreamID) Validate() error { | |||
| 107 | + | if id == 0 { | |||
| 108 | + | return errors.New("ports: broker stream ID is zero") | |||
| 109 | + | } | |||
| 110 | + | return nil | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | func (id BrokerOperationID) IsZero() bool { return id == (BrokerOperationID{}) } | |||
| 114 | + | ||||
| 115 | + | func (id BrokerOperationID) Validate() error { | |||
| 116 | + | if id.IsZero() { | |||
| 117 | + | return errors.New("ports: broker operation ID is zero") | |||
| 118 | + | } | |||
| 119 | + | return nil | |||
| 120 | + | } | |||
| 121 | + | ||||
| 122 | + | func (id BrokerDaemonIncarnation) IsZero() bool { return id == (BrokerDaemonIncarnation{}) } | |||
| 123 | + | ||||
| 124 | + | func (id BrokerDaemonIncarnation) Validate() error { | |||
| 125 | + | if id.IsZero() { | |||
| 126 | + | return errors.New("ports: broker daemon incarnation is zero") | |||
| 127 | + | } | |||
| 128 | + | return nil | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // BrokerDaemonIdentity is the authenticated daemon/service identity used | |||
| 132 | + | // for physical transport pooling. Display aliases are never pooled: two | |||
| 133 | + | // endpoints share a transport only after authenticated identity and every | |||
| 134 | + | // trust, launch, isolation, and transport policy compare compatible. | |||
| 135 | + | type BrokerDaemonIdentity string | |||
| 136 | + | ||||
| 137 | + | func (id BrokerDaemonIdentity) Validate() error { | |||
| 138 | + | return validateBrokerToken(string(id), BrokerMaxIdentityBytes, "daemon identity") | |||
| 139 | + | } | |||
| 140 | + | ||||
| 141 | + | // BrokerPolicy is the exact connection policy required before two | |||
| 142 | + | // endpoints may share a pooled physical transport. Pooling requires exact | |||
| 143 | + | // equality: conflicting policy is rejected, never merged or inherited | |||
| 144 | + | // from the first launching client. | |||
| 145 | + | type BrokerPolicy struct { | |||
| 146 | + | ProtocolVersion uint16 | |||
| 147 | + | CatalogSchemaVersion uint16 | |||
| 148 | + | EnvironmentPolicy protocol.EnvironmentPolicy | |||
| 149 | + | Transport string | |||
| 150 | + | } | |||
| 151 | + | ||||
| 152 | + | func (p BrokerPolicy) Validate() error { | |||
| 153 | + | if p.ProtocolVersion == 0 { | |||
| 154 | + | return errors.New("ports: broker policy has no protocol version") | |||
| 155 | + | } | |||
| 156 | + | if p.CatalogSchemaVersion == 0 { | |||
| 157 | + | return errors.New("ports: broker policy has no catalog schema version") | |||
| 158 | + | } | |||
| 159 | + | if err := protocol.ValidateEnvironmentPolicy(p.EnvironmentPolicy); err != nil { | |||
| 160 | + | return fmt.Errorf("ports: broker policy: %w", err) | |||
| 161 | + | } | |||
| 162 | + | if err := validateBrokerToken(p.Transport, BrokerMaxTransportBytes, "policy transport"); err != nil { | |||
| 163 | + | return err | |||
| 164 | + | } | |||
| 165 | + | return nil | |||
| 166 | + | } | |||
| 167 | + | ||||
| 168 | + | // Compatible reports whether two validated policies may share a pooled | |||
| 169 | + | // physical transport. It requires exact equality across every field. | |||
| 170 | + | func (p BrokerPolicy) Compatible(other BrokerPolicy) bool { | |||
| 171 | + | return p.ProtocolVersion == other.ProtocolVersion && | |||
| 172 | + | p.CatalogSchemaVersion == other.CatalogSchemaVersion && | |||
| 173 | + | p.EnvironmentPolicy == other.EnvironmentPolicy && | |||
| 174 | + | p.Transport == other.Transport | |||
| 175 | + | } | |||
| 176 | + | ||||
| 177 | + | // BrokerHostTombstone fences a removed host registration so a stale probe, | |||
| 178 | + | // snapshot, or completion bound to the retired incarnation cannot overwrite | |||
| 179 | + | // or revive current authority. A re-added endpoint carries a new | |||
| 180 | + | // incarnation and is never fenced by an older tombstone. | |||
| 181 | + | type BrokerHostTombstone struct { | |||
| 182 | + | Endpoint string | |||
| 183 | + | Registration domain.RemoteRegistration | |||
| 184 | + | RetiredRevision BrokerRevision | |||
| 185 | + | } | |||
| 186 | + | ||||
| 187 | + | func (t BrokerHostTombstone) Validate() error { | |||
| 188 | + | if err := domain.ValidateRemoteHostTarget(t.Endpoint); err != nil { | |||
| 189 | + | return fmt.Errorf("ports: broker tombstone: %w", err) | |||
| 190 | + | } | |||
| 191 | + | if err := t.Registration.Validate(); err != nil { | |||
| 192 | + | return fmt.Errorf("ports: broker tombstone: %w", err) | |||
| 193 | + | } | |||
| 194 | + | if t.Endpoint != t.Registration.Endpoint { | |||
| 195 | + | return errors.New("ports: broker tombstone endpoint does not match registration") | |||
| 196 | + | } | |||
| 197 | + | if t.RetiredRevision == 0 { | |||
| 198 | + | return errors.New("ports: broker tombstone has no retired revision") | |||
| 199 | + | } | |||
| 200 | + | return nil | |||
| 201 | + | } | |||
| 202 | + | ||||
| 203 | + | // Fences reports whether this tombstone retires the supplied registration: | |||
| 204 | + | // same endpoint and incarnation, with a retired generation at or past the | |||
| 205 | + | // candidate. A re-added endpoint with a fresh incarnation is never fenced. | |||
| 206 | + | func (t BrokerHostTombstone) Fences(candidate domain.RemoteRegistration) bool { | |||
| 207 | + | return t.Endpoint == candidate.Endpoint && | |||
| 208 | + | t.Registration.Incarnation == candidate.Incarnation && | |||
| 209 | + | t.Registration.Generation >= candidate.Generation | |||
| 210 | + | } | |||
| 211 | + | ||||
| 212 | + | // BrokerSnapshot is an immutable, fully defensive broker publication. Hosts | |||
| 213 | + | // reuse the ordered RemoteHostSnapshot projection; Removed carries the | |||
| 214 | + | // bounded tombstone set that fences stale authority. Nested slices are | |||
| 215 | + | // never mutated after publication. | |||
| 216 | + | type BrokerSnapshot struct { | |||
| 217 | + | Epoch BrokerEpoch | |||
| 218 | + | Revision BrokerRevision | |||
| 219 | + | Hosts []RemoteHostSnapshot | |||
| 220 | + | Removed []BrokerHostTombstone | |||
| 221 | + | } | |||
| 222 | + | ||||
| 223 | + | func (s BrokerSnapshot) Validate() error { | |||
| 224 | + | if s.Epoch == 0 { | |||
| 225 | + | return errors.New("ports: broker snapshot has no epoch") | |||
| 226 | + | } | |||
| 227 | + | if s.Revision == 0 { | |||
| 228 | + | return errors.New("ports: broker snapshot has no revision") | |||
| 229 | + | } | |||
| 230 | + | if len(s.Hosts) > BrokerMaxHosts { | |||
| 231 | + | return errors.New("ports: broker snapshot has too many hosts") | |||
| 232 | + | } | |||
| 233 | + | if len(s.Removed) > BrokerMaxTombstones { | |||
| 234 | + | return errors.New("ports: broker snapshot has too many tombstones") | |||
| 235 | + | } | |||
| 236 | + | seen := make(map[string]struct{}, len(s.Hosts)) | |||
| 237 | + | for _, host := range s.Hosts { | |||
| 238 | + | if err := domain.ValidateRemoteHostTarget(host.Endpoint); err != nil { | |||
| 239 | + | return fmt.Errorf("ports: broker snapshot: %w", err) | |||
| 240 | + | } | |||
| 241 | + | if err := host.Registration.Validate(); err != nil { | |||
| 242 | + | return fmt.Errorf("ports: broker snapshot: %w", err) | |||
| 243 | + | } | |||
| 244 | + | if host.Endpoint != host.Registration.Endpoint { | |||
| 245 | + | return errors.New("ports: broker snapshot host endpoint does not match registration") | |||
| 246 | + | } | |||
| 247 | + | if len(host.Sessions) > BrokerMaxSessionsPerHost { | |||
| 248 | + | return errors.New("ports: broker snapshot host has too many sessions") | |||
| 249 | + | } | |||
| 250 | + | if _, dup := seen[host.Endpoint]; dup { | |||
| 251 | + | return fmt.Errorf("ports: broker snapshot has duplicate host %q", host.Endpoint) | |||
| 252 | + | } | |||
| 253 | + | seen[host.Endpoint] = struct{}{} | |||
| 254 | + | } | |||
| 255 | + | for _, tombstone := range s.Removed { | |||
| 256 | + | if err := tombstone.Validate(); err != nil { | |||
| 257 | + | return err | |||
| 258 | + | } | |||
| 259 | + | if _, live := seen[tombstone.Endpoint]; live { | |||
| 260 | + | return fmt.Errorf("ports: broker snapshot carries live host %q as tombstone", tombstone.Endpoint) | |||
| 261 | + | } | |||
| 262 | + | } | |||
| 263 | + | return nil | |||
| 264 | + | } | |||
| 265 | + | ||||
| 266 | + | // Clone returns a defensive copy with independent host, session, tab, and | |||
| 267 | + | // tombstone slices. | |||
| 268 | + | func (s BrokerSnapshot) Clone() BrokerSnapshot { | |||
| 269 | + | out := s | |||
| 270 | + | out.Hosts = make([]RemoteHostSnapshot, len(s.Hosts)) | |||
| 271 | + | for i, host := range s.Hosts { | |||
| 272 | + | out.Hosts[i] = host.Clone() | |||
| 273 | + | } | |||
| 274 | + | out.Removed = append([]BrokerHostTombstone(nil), s.Removed...) | |||
| 275 | + | return out | |||
| 276 | + | } | |||
| 277 | + | ||||
| 278 | + | // Find returns the snapshot for an exact endpoint identity. | |||
| 279 | + | func (s BrokerSnapshot) Find(endpoint string) (RemoteHostSnapshot, bool) { | |||
| 280 | + | for _, host := range s.Hosts { | |||
| 281 | + | if host.Endpoint == endpoint { | |||
| 282 | + | return host, true | |||
| 283 | + | } | |||
| 284 | + | } | |||
| 285 | + | return RemoteHostSnapshot{}, false | |||
| 286 | + | } | |||
| 287 | + | ||||
| 288 | + | // Supersedes reports whether s is newer than other. Any epoch change | |||
| 289 | + | // forces a full resynchronization: a snapshot with a valid epoch | |||
| 290 | + | // supersedes every snapshot from another epoch (or none), regardless of | |||
| 291 | + | // numeric order, because epochs are fresh per broker process and never | |||
| 292 | + | // ordered across processes. Within one epoch the higher revision wins. | |||
| 293 | + | // Revisions never compare across epochs. A zero-epoch snapshot is | |||
| 294 | + | // invalid and never supersedes. | |||
| 295 | + | func (s BrokerSnapshot) Supersedes(other BrokerSnapshot) bool { | |||
| 296 | + | if s.Epoch == 0 { | |||
| 297 | + | return false | |||
| 298 | + | } | |||
| 299 | + | if s.Epoch != other.Epoch { | |||
| 300 | + | return true | |||
| 301 | + | } | |||
| 302 | + | return s.Revision > other.Revision | |||
| 303 | + | } | |||
| 304 | + | ||||
| 305 | + | // BrokerCompletionIsStale reports whether a probe or operation completion | |||
| 306 | + | // must be rejected: a different broker epoch, or a registration that no | |||
| 307 | + | // longer exactly matches current authority (endpoint, incarnation, and | |||
| 308 | + | // generation must all match). | |||
| 309 | + | func BrokerCompletionIsStale(currentRegistration domain.RemoteRegistration, currentEpoch BrokerEpoch, completionRegistration domain.RemoteRegistration, completionEpoch BrokerEpoch) bool { | |||
| 310 | + | if currentEpoch == 0 || completionEpoch == 0 { | |||
| 311 | + | return true | |||
| 312 | + | } | |||
| 313 | + | if currentEpoch != completionEpoch { | |||
| 314 | + | return true | |||
| 315 | + | } | |||
| 316 | + | return !currentRegistration.Equal(completionRegistration) | |||
| 317 | + | } | |||
| 318 | + | ||||
| 319 | + | // BrokerOpenStreamRequest asks the broker to open one independently | |||
| 320 | + | // cancellable logical stream to the owning daemon. The daemon revalidates | |||
| 321 | + | // the exact session identity before attachment. Env is the per-request | |||
| 322 | + | // session environment; it is never inherited from the broker process | |||
| 323 | + | // environment. | |||
| 324 | + | type BrokerOpenStreamRequest struct { | |||
| 325 | + | Connection BrokerConnectionID | |||
| 326 | + | Stream BrokerStreamID | |||
| 327 | + | Endpoint string | |||
| 328 | + | Registration domain.RemoteRegistration | |||
| 329 | + | Target protocol.ExactSessionTarget | |||
| 330 | + | Env []string | |||
| 331 | + | Policy BrokerPolicy | |||
| 332 | + | } | |||
| 333 | + | ||||
| 334 | + | func (r BrokerOpenStreamRequest) Validate() error { | |||
| 335 | + | if err := r.Connection.Validate(); err != nil { | |||
| 336 | + | return err | |||
| 337 | + | } | |||
| 338 | + | if err := r.Stream.Validate(); err != nil { | |||
| 339 | + | return err | |||
| 340 | + | } | |||
| 341 | + | if err := domain.ValidateRemoteHostTarget(r.Endpoint); err != nil { | |||
| 342 | + | return fmt.Errorf("ports: broker open stream: %w", err) | |||
| 343 | + | } | |||
| 344 | + | if err := r.Registration.Validate(); err != nil { | |||
| 345 | + | return fmt.Errorf("ports: broker open stream: %w", err) | |||
| 346 | + | } | |||
| 347 | + | if r.Endpoint != r.Registration.Endpoint { | |||
| 348 | + | return errors.New("ports: broker open stream endpoint does not match registration") | |||
| 349 | + | } | |||
| 350 | + | if err := r.Target.Validate(); err != nil { | |||
| 351 | + | return fmt.Errorf("ports: broker open stream: %w", err) | |||
| 352 | + | } | |||
| 353 | + | if uint64(len(r.Env)) > BrokerMaxEnvEntries { | |||
| 354 | + | return errors.New("ports: broker open stream has too many environment entries") | |||
| 355 | + | } | |||
| 356 | + | for _, entry := range r.Env { | |||
| 357 | + | if len(entry) > BrokerMaxEnvEntryBytes { | |||
| 358 | + | return errors.New("ports: broker open stream environment entry too long") | |||
| 359 | + | } | |||
| 360 | + | if !utf8.ValidString(entry) { | |||
| 361 | + | return errors.New("ports: broker open stream environment is not valid UTF-8") | |||
| 362 | + | } | |||
| 363 | + | } | |||
| 364 | + | if err := r.Policy.Validate(); err != nil { | |||
| 365 | + | return err | |||
| 366 | + | } | |||
| 367 | + | return nil | |||
| 368 | + | } | |||
| 369 | + | ||||
| 370 | + | // BrokerMutationOutcome is the closed outcome taxonomy for a mutating | |||
| 371 | + | // broker operation. OutcomeUnknown means connectivity was lost after the | |||
| 372 | + | // mutation may have committed but before its result arrived: the client | |||
| 373 | + | // refreshes authoritative state and never blindly replays a | |||
| 374 | + | // non-idempotent operation. | |||
| 375 | + | type BrokerMutationOutcome uint8 | |||
| 376 | + | ||||
| 377 | + | const ( | |||
| 378 | + | BrokerOutcomeOK BrokerMutationOutcome = iota + 1 | |||
| 379 | + | BrokerOutcomeFailed | |||
| 380 | + | BrokerOutcomeUnknown | |||
| 381 | + | ) | |||
| 382 | + | ||||
| 383 | + | func (o BrokerMutationOutcome) String() string { | |||
| 384 | + | switch o { | |||
| 385 | + | case BrokerOutcomeOK: | |||
| 386 | + | return "ok" | |||
| 387 | + | case BrokerOutcomeFailed: | |||
| 388 | + | return "failed" | |||
| 389 | + | case BrokerOutcomeUnknown: | |||
| 390 | + | return "outcome_unknown" | |||
| 391 | + | default: | |||
| 392 | + | return "unknown" | |||
| 393 | + | } | |||
| 394 | + | } | |||
| 395 | + | ||||
| 396 | + | func (o BrokerMutationOutcome) Validate() error { | |||
| 397 | + | switch o { | |||
| 398 | + | case BrokerOutcomeOK, BrokerOutcomeFailed, BrokerOutcomeUnknown: | |||
| 399 | + | return nil | |||
| 400 | + | default: | |||
| 401 | + | return errors.New("ports: invalid broker mutation outcome") | |||
| 402 | + | } | |||
| 403 | + | } | |||
| 404 | + | ||||
| 405 | + | // BrokerOperationResult is exactly one completion for one admitted mutating | |||
| 406 | + | // operation. Text is bounded and presentation-safe; the outcome decides | |||
| 407 | + | // whether the client may retry, must refresh, or must not replay. | |||
| 408 | + | type BrokerOperationResult struct { | |||
| 409 | + | Operation BrokerOperationID | |||
| 410 | + | Outcome BrokerMutationOutcome | |||
| 411 | + | Text string | |||
| 412 | + | } | |||
| 413 | + | ||||
| 414 | + | func (r BrokerOperationResult) Validate() error { | |||
| 415 | + | if err := r.Operation.Validate(); err != nil { | |||
| 416 | + | return err | |||
| 417 | + | } | |||
| 418 | + | if err := r.Outcome.Validate(); err != nil { | |||
| 419 | + | return err | |||
| 420 | + | } | |||
| 421 | + | if err := validateBrokerDisplayText(r.Text, BrokerMaxErrorBytes, "operation result text"); err != nil { | |||
| 422 | + | return err | |||
| 423 | + | } | |||
| 424 | + | if r.Outcome == BrokerOutcomeOK && r.Text != "" { | |||
| 425 | + | return errors.New("ports: successful broker operation carries no text") | |||
| 426 | + | } | |||
| 427 | + | return nil | |||
| 428 | + | } | |||
| 429 | + | ||||
| 430 | + | // BrokerErrorCode is the closed taxonomy for broker and stream lifecycle | |||
| 431 | + | // failures surfaced to clients. Every failure returns the client to the | |||
| 432 | + | // picker in the same process with a visible typed error; none terminates | |||
| 433 | + | // the client except an explicit exit or a fatal terminal failure. | |||
| 434 | + | type BrokerErrorCode uint8 | |||
| 435 | + | ||||
| 436 | + | const ( | |||
| 437 | + | BrokerErrorUnavailable BrokerErrorCode = iota + 1 | |||
| 438 | + | BrokerErrorIncompatible | |||
| 439 | + | BrokerErrorTimeout | |||
| 440 | + | BrokerErrorCancelled | |||
| 441 | + | BrokerErrorOutcomeUnknown | |||
| 442 | + | BrokerErrorAttachmentLost | |||
| 443 | + | BrokerErrorStaleEpoch | |||
| 444 | + | BrokerErrorConflictingPolicy | |||
| 445 | + | BrokerErrorExplicitExit | |||
| 446 | + | BrokerErrorFatalTerminal | |||
| 447 | + | ) | |||
| 448 | + | ||||
| 449 | + | func (c BrokerErrorCode) String() string { | |||
| 450 | + | switch c { | |||
| 451 | + | case BrokerErrorUnavailable: | |||
| 452 | + | return "unavailable" | |||
| 453 | + | case BrokerErrorIncompatible: | |||
| 454 | + | return "incompatible" | |||
| 455 | + | case BrokerErrorTimeout: | |||
| 456 | + | return "timeout" | |||
| 457 | + | case BrokerErrorCancelled: | |||
| 458 | + | return "cancelled" | |||
| 459 | + | case BrokerErrorOutcomeUnknown: | |||
| 460 | + | return "outcome_unknown" | |||
| 461 | + | case BrokerErrorAttachmentLost: | |||
| 462 | + | return "attachment_lost" | |||
| 463 | + | case BrokerErrorStaleEpoch: | |||
| 464 | + | return "stale_epoch" | |||
| 465 | + | case BrokerErrorConflictingPolicy: | |||
| 466 | + | return "conflicting_policy" | |||
| 467 | + | case BrokerErrorExplicitExit: | |||
| 468 | + | return "explicit_exit" | |||
| 469 | + | case BrokerErrorFatalTerminal: | |||
| 470 | + | return "fatal_terminal" | |||
| 471 | + | default: | |||
| 472 | + | return "unknown" | |||
| 473 | + | } | |||
| 474 | + | } | |||
| 475 | + | ||||
| 476 | + | func (c BrokerErrorCode) Validate() error { | |||
| 477 | + | switch c { | |||
| 478 | + | case BrokerErrorUnavailable, BrokerErrorIncompatible, BrokerErrorTimeout, | |||
| 479 | + | BrokerErrorCancelled, BrokerErrorOutcomeUnknown, BrokerErrorAttachmentLost, | |||
| 480 | + | BrokerErrorStaleEpoch, BrokerErrorConflictingPolicy, | |||
| 481 | + | BrokerErrorExplicitExit, BrokerErrorFatalTerminal: | |||
| 482 | + | return nil | |||
| 483 | + | default: | |||
| 484 | + | return errors.New("ports: invalid broker error code") | |||
| 485 | + | } | |||
| 486 | + | } | |||
| 487 | + | ||||
| 488 | + | // BrokerError is the typed lifecycle failure carried to clients. Terminal | |||
| 489 | + | // reports whether the client process itself must exit: only an explicit | |||
| 490 | + | // exit or a fatal terminal failure is terminal; every other code returns | |||
| 491 | + | // to the picker with a visible notification. | |||
| 492 | + | type BrokerError struct { | |||
| 493 | + | Code BrokerErrorCode | |||
| 494 | + | Text string | |||
| 495 | + | } | |||
| 496 | + | ||||
| 497 | + | func (e BrokerError) Error() string { | |||
| 498 | + | if e.Text == "" { | |||
| 499 | + | return "vev: broker " + e.Code.String() | |||
| 500 | + | } | |||
| 501 | + | return "vev: broker " + e.Code.String() + ": " + e.Text | |||
| 502 | + | } | |||
| 503 | + | ||||
| 504 | + | func (e BrokerError) Validate() error { | |||
| 505 | + | if err := e.Code.Validate(); err != nil { | |||
| 506 | + | return err | |||
| 507 | + | } | |||
| 508 | + | return validateBrokerDisplayText(e.Text, BrokerMaxErrorBytes, "error text") | |||
| 509 | + | } | |||
| 510 | + | ||||
| 511 | + | // Terminal reports whether this failure ends the client process. | |||
| 512 | + | func (e BrokerError) Terminal() bool { | |||
| 513 | + | return e.Code == BrokerErrorExplicitExit || e.Code == BrokerErrorFatalTerminal | |||
| 514 | + | } | |||
| 515 | + | ||||
| 516 | + | // BrokerStreamLost is the independent typed loss event delivered per | |||
| 517 | + | // affected logical stream when its physical connection fails. One stream | |||
| 518 | + | // failure never closes unrelated streams; a physical loss fans out one | |||
| 519 | + | // event per stream. | |||
| 520 | + | type BrokerStreamLost struct { | |||
| 521 | + | Connection BrokerConnectionID | |||
| 522 | + | Stream BrokerStreamID | |||
| 523 | + | Epoch BrokerEpoch | |||
| 524 | + | Cause domain.RemoteFailureKind | |||
| 525 | + | } | |||
| 526 | + | ||||
| 527 | + | func (e BrokerStreamLost) Validate() error { | |||
| 528 | + | if err := e.Connection.Validate(); err != nil { | |||
| 529 | + | return err | |||
| 530 | + | } | |||
| 531 | + | if err := e.Stream.Validate(); err != nil { | |||
| 532 | + | return err | |||
| 533 | + | } | |||
| 534 | + | if e.Epoch == 0 { | |||
| 535 | + | return errors.New("ports: broker stream loss has no epoch") | |||
| 536 | + | } | |||
| 537 | + | return nil | |||
| 538 | + | } | |||
| 539 | + | ||||
| 540 | + | // BrokerSubscription wakes one subscriber when a newer snapshot is | |||
| 541 | + | // available. Each subscriber owns a capacity-one channel and re-reads the | |||
| 542 | + | // newest snapshot on wake; channels are never shared between subscribers. | |||
| 543 | + | type BrokerSubscription interface { | |||
| 544 | + | Changed() <-chan struct{} | |||
| 545 | + | Close() | |||
| 546 | + | } | |||
| 547 | + | ||||
| 548 | + | // BrokerService is the client-facing broker façade. All clients reach | |||
| 549 | + | // local and remote daemons through it, including the local daemon. | |||
| 550 | + | // Snapshot access performs no I/O; request methods are non-blocking | |||
| 551 | + | // coalesced hints except where the context explicitly bounds them. | |||
| 552 | + | type BrokerService interface { | |||
| 553 | + | // ConnectionID returns the ID assigned when this client connection was | |||
| 554 | + | // accepted. Clients carry it on stream operations to fence stale requests. | |||
| 555 | + | ConnectionID() BrokerConnectionID | |||
| 556 | + | Snapshot() BrokerSnapshot | |||
| 557 | + | Subscribe() (BrokerSubscription, error) | |||
| 558 | + | OpenStream(ctx context.Context, request BrokerOpenStreamRequest) (ClientConnection, error) | |||
| 559 | + | CloseStream(connection BrokerConnectionID, stream BrokerStreamID) error | |||
| 560 | + | AddHost(ctx context.Context, target string) error | |||
| 561 | + | RemoveHost(ctx context.Context, target string) (bool, error) | |||
| 562 | + | RequestReconcile(endpoint string) | |||
| 563 | + | Close() error | |||
| 564 | + | } | |||
| 565 | + | ||||
| 566 | + | // BrokerSnapshotStore persists the durable broker snapshot independently | |||
| 567 | + | // from session state. The broker is its sole writer; session persistence | |||
| 568 | + | // stays daemon-owned and is never moved here. | |||
| 569 | + | type BrokerSnapshotStore interface { | |||
| 570 | + | Load() (BrokerSnapshot, error) | |||
| 571 | + | Store(BrokerSnapshot) error | |||
| 572 | + | } | |||
| 573 | + | ||||
| 574 | + | // BrokerPhysicalConnection is one pooled physical transport to an | |||
| 575 | + | // authenticated daemon identity. It opens one independently cancellable | |||
| 576 | + | // typed session connection per logical stream and never shares stream | |||
| 577 | + | // state, identity, geometry, input, output, cancellation, or application | |||
| 578 | + | // errors between streams. | |||
| 579 | + | type BrokerPhysicalConnection interface { | |||
| 580 | + | Identity() BrokerDaemonIdentity | |||
| 581 | + | Incarnation() BrokerDaemonIncarnation | |||
| 582 | + | Policy() BrokerPolicy | |||
| 583 | + | OpenStream(ctx context.Context, request BrokerOpenStreamRequest) (ClientConnection, error) | |||
| 584 | + | Close() error | |||
| 585 | + | } | |||
| 586 | + | ||||
| 587 | + | // BrokerEndpointConnector establishes the pooled physical transport for | |||
| 588 | + | // one endpoint under an exact compatible policy. Conflicting policy is | |||
| 589 | + | // rejected; the first launching client never becomes policy authority. | |||
| 590 | + | type BrokerEndpointConnector interface { | |||
| 591 | + | Connect(ctx context.Context, endpoint string, policy BrokerPolicy) (BrokerPhysicalConnection, error) | |||
| 592 | + | } | |||
| 593 | + | ||||
| 594 | + | // BrokerListener accepts client connections to the per-user broker | |||
| 595 | + | // endpoint. Each Accept returns a BrokerService bound to exactly one | |||
| 596 | + | // accepted client connection: the implementation assigns that | |||
| 597 | + | // connection's BrokerConnectionID at accept, ends the connection on | |||
| 598 | + | // Close, and rejects requests carrying a mismatched Connection as stale. | |||
| 599 | + | // Implementations own the owner-only directory/socket, peer credential | |||
| 600 | + | // checks, bounded clients and queues, and cancellation-safe accept loops. | |||
| 601 | + | type BrokerListener interface { | |||
| 602 | + | Accept() (BrokerService, error) | |||
| 603 | + | Close() error | |||
| 604 | + | Addr() string | |||
| 605 | + | } | |||
| 606 | + | ||||
| 607 | + | func validateBrokerToken(value string, maxBytes int, label string) error { | |||
| 608 | + | if value == "" { | |||
| 609 | + | return fmt.Errorf("ports: broker %s is empty", label) | |||
| 610 | + | } | |||
| 611 | + | if len(value) > maxBytes { | |||
| 612 | + | return fmt.Errorf("ports: broker %s too long", label) | |||
| 613 | + | } | |||
| 614 | + | if !utf8.ValidString(value) { | |||
| 615 | + | return fmt.Errorf("ports: broker %s is not valid UTF-8", label) | |||
| 616 | + | } | |||
| 617 | + | if strings.TrimSpace(value) != value { | |||
| 618 | + | return fmt.Errorf("ports: broker %s has surrounding whitespace", label) | |||
| 619 | + | } | |||
| 620 | + | for _, r := range value { | |||
| 621 | + | if unicode.IsControl(r) || unicode.IsSpace(r) || unicode.Is(unicode.Bidi_Control, r) || r == '\u2028' || r == '\u2029' { | |||
| 622 | + | return fmt.Errorf("ports: broker %s contains disallowed characters", label) | |||
| 623 | + | } | |||
| 624 | + | } | |||
| 625 | + | return nil | |||
| 626 | + | } | |||
| 627 | + | ||||
| 628 | + | func validateBrokerDisplayText(value string, maxBytes int, label string) error { | |||
| 629 | + | if len(value) > maxBytes { | |||
| 630 | + | return fmt.Errorf("ports: broker %s too long", label) | |||
| 631 | + | } | |||
| 632 | + | if !utf8.ValidString(value) { | |||
| 633 | + | return fmt.Errorf("ports: broker %s is not valid UTF-8", label) | |||
| 634 | + | } | |||
| 635 | + | for _, r := range value { | |||
| 636 | + | if unicode.IsControl(r) || unicode.Is(unicode.Bidi_Control, r) || r == '\u2028' || r == '\u2029' { | |||
| 637 | + | return fmt.Errorf("ports: broker %s contains disallowed characters", label) | |||
| 638 | + | } | |||
| 639 | + | } | |||
| 640 | + | return nil | |||
| 641 | + | } | |||
A internal/ports/broker_test.go +479 -0
| @@ -0,0 +1,479 @@ | |||
| 1 | + | package ports | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "reflect" | |
| 5 | + | "strings" | |
| 6 | + | "testing" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/vev/internal/domain" | |
| 9 | + | "github.com/bnema/vev/internal/protocol" | |
| 10 | + | "github.com/bnema/vev/internal/protocol/catalogue" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | func testBrokerRegistration(endpoint string, incarnation byte, generation domain.RemoteGeneration) domain.RemoteRegistration { | |
| 14 | + | return domain.RemoteRegistration{ | |
| 15 | + | Endpoint: endpoint, | |
| 16 | + | Incarnation: [16]byte{incarnation, 7, 9}, | |
| 17 | + | Generation: generation, | |
| 18 | + | } | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | func testBrokerLifecycle() domain.SessionLifecycleID { | |
| 22 | + | return domain.SessionLifecycleID{1, 2, 3} | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | func testBrokerTarget() protocol.ExactSessionTarget { | |
| 26 | + | return protocol.ExactSessionTarget{LifecycleID: testBrokerLifecycle(), SessionName: "work"} | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | func testBrokerPolicy() BrokerPolicy { | |
| 30 | + | return BrokerPolicy{ | |
| 31 | + | ProtocolVersion: protocol.Version, | |
| 32 | + | CatalogSchemaVersion: catalogue.RemoteCatalogSchemaVersion, | |
| 33 | + | EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, | |
| 34 | + | Transport: "quic", | |
| 35 | + | } | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | func testBrokerHost(endpoint string) RemoteHostSnapshot { | |
| 39 | + | return RemoteHostSnapshot{ | |
| 40 | + | Endpoint: endpoint, | |
| 41 | + | DisplayOrigin: endpoint, | |
| 42 | + | Registration: testBrokerRegistration(endpoint, 1, 1), | |
| 43 | + | Availability: domain.RemoteAvailabilityReachable, | |
| 44 | + | } | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | func validBrokerSnapshot() BrokerSnapshot { | |
| 48 | + | return BrokerSnapshot{ | |
| 49 | + | Epoch: 3, | |
| 50 | + | Revision: 9, | |
| 51 | + | Hosts: []RemoteHostSnapshot{testBrokerHost("user@arch")}, | |
| 52 | + | } | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | func TestBrokerSnapshotValidateBounds(t *testing.T) { | |
| 56 | + | tests := []struct { | |
| 57 | + | name string | |
| 58 | + | mutate func(*BrokerSnapshot) | |
| 59 | + | wantErr bool | |
| 60 | + | }{ | |
| 61 | + | {name: "valid", mutate: func(*BrokerSnapshot) {}, wantErr: false}, | |
| 62 | + | {name: "zero epoch", mutate: func(s *BrokerSnapshot) { s.Epoch = 0 }, wantErr: true}, | |
| 63 | + | {name: "zero revision", mutate: func(s *BrokerSnapshot) { s.Revision = 0 }, wantErr: true}, | |
| 64 | + | {name: "too many hosts", mutate: func(s *BrokerSnapshot) { | |
| 65 | + | s.Hosts = make([]RemoteHostSnapshot, BrokerMaxHosts+1) | |
| 66 | + | for i := range s.Hosts { | |
| 67 | + | s.Hosts[i] = testBrokerHost("user@arch") | |
| 68 | + | s.Hosts[i].Registration = testBrokerRegistration("user@arch", 1, 1) | |
| 69 | + | } | |
| 70 | + | }, wantErr: true}, | |
| 71 | + | {name: "duplicate host", mutate: func(s *BrokerSnapshot) { | |
| 72 | + | s.Hosts = append(s.Hosts, testBrokerHost("user@arch")) | |
| 73 | + | }, wantErr: true}, | |
| 74 | + | {name: "endpoint registration mismatch", mutate: func(s *BrokerSnapshot) { | |
| 75 | + | s.Hosts[0].Registration = testBrokerRegistration("user@other", 1, 1) | |
| 76 | + | }, wantErr: true}, | |
| 77 | + | {name: "zero registration incarnation", mutate: func(s *BrokerSnapshot) { | |
| 78 | + | s.Hosts[0].Registration = domain.RemoteRegistration{Endpoint: "user@arch", Generation: 1} | |
| 79 | + | }, wantErr: true}, | |
| 80 | + | {name: "live host as tombstone", mutate: func(s *BrokerSnapshot) { | |
| 81 | + | s.Removed = []BrokerHostTombstone{{ | |
| 82 | + | Endpoint: "user@arch", | |
| 83 | + | Registration: testBrokerRegistration("user@arch", 1, 1), | |
| 84 | + | RetiredRevision: 9, | |
| 85 | + | }} | |
| 86 | + | }, wantErr: true}, | |
| 87 | + | {name: "too many tombstones", mutate: func(s *BrokerSnapshot) { | |
| 88 | + | s.Removed = make([]BrokerHostTombstone, BrokerMaxTombstones+1) | |
| 89 | + | for i := range s.Removed { | |
| 90 | + | s.Removed[i] = BrokerHostTombstone{ | |
| 91 | + | Endpoint: "user@gone", | |
| 92 | + | Registration: testBrokerRegistration("user@gone", 1, 1), | |
| 93 | + | RetiredRevision: 9, | |
| 94 | + | } | |
| 95 | + | } | |
| 96 | + | }, wantErr: true}, | |
| 97 | + | } | |
| 98 | + | for _, tt := range tests { | |
| 99 | + | t.Run(tt.name, func(t *testing.T) { | |
| 100 | + | snapshot := validBrokerSnapshot() | |
| 101 | + | tt.mutate(&snapshot) | |
| 102 | + | if err := snapshot.Validate(); (err != nil) != tt.wantErr { | |
| 103 | + | t.Fatalf("Validate() = %v, wantErr %t", err, tt.wantErr) | |
| 104 | + | } | |
| 105 | + | }) | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | func TestBrokerSnapshotEpochRevisionOrdering(t *testing.T) { | |
| 110 | + | tests := []struct { | |
| 111 | + | name string | |
| 112 | + | current BrokerSnapshot | |
| 113 | + | candidate BrokerSnapshot | |
| 114 | + | wantNewer bool | |
| 115 | + | description string | |
| 116 | + | }{ | |
| 117 | + | { | |
| 118 | + | name: "higher revision wins in epoch", | |
| 119 | + | current: BrokerSnapshot{Epoch: 3, Revision: 9}, | |
| 120 | + | candidate: BrokerSnapshot{Epoch: 3, Revision: 10}, | |
| 121 | + | wantNewer: true, | |
| 122 | + | description: "monotonic revision within an epoch", | |
| 123 | + | }, | |
| 124 | + | { | |
| 125 | + | name: "lower revision loses in epoch", | |
| 126 | + | current: BrokerSnapshot{Epoch: 3, Revision: 10}, | |
| 127 | + | candidate: BrokerSnapshot{Epoch: 3, Revision: 9}, | |
| 128 | + | wantNewer: false, | |
| 129 | + | description: "stale revision never applies", | |
| 130 | + | }, | |
| 131 | + | { | |
| 132 | + | name: "equal revision loses", | |
| 133 | + | current: BrokerSnapshot{Epoch: 3, Revision: 9}, | |
| 134 | + | candidate: BrokerSnapshot{Epoch: 3, Revision: 9}, | |
| 135 | + | wantNewer: false, | |
| 136 | + | description: "replay of the same revision is not newer", | |
| 137 | + | }, | |
| 138 | + | { | |
| 139 | + | name: "epoch change forces resync", | |
| 140 | + | current: BrokerSnapshot{Epoch: 3, Revision: 99}, | |
| 141 | + | candidate: BrokerSnapshot{Epoch: 4, Revision: 1}, | |
| 142 | + | wantNewer: true, | |
| 143 | + | description: "any valid epoch change supersedes regardless of revision", | |
| 144 | + | }, | |
| 145 | + | { | |
| 146 | + | name: "lower epoch still forces resync", | |
| 147 | + | current: BrokerSnapshot{Epoch: 9, Revision: 1}, | |
| 148 | + | candidate: BrokerSnapshot{Epoch: 4, Revision: 50}, | |
| 149 | + | wantNewer: true, | |
| 150 | + | description: "epochs are fresh per process, never numerically ordered", | |
| 151 | + | }, | |
| 152 | + | { | |
| 153 | + | name: "zero epoch never supersedes", | |
| 154 | + | current: BrokerSnapshot{Epoch: 3, Revision: 9}, | |
| 155 | + | candidate: BrokerSnapshot{}, | |
| 156 | + | wantNewer: false, | |
| 157 | + | description: "invalid snapshots never apply", | |
| 158 | + | }, | |
| 159 | + | } | |
| 160 | + | for _, tt := range tests { | |
| 161 | + | t.Run(tt.name, func(t *testing.T) { | |
| 162 | + | if got := tt.candidate.Supersedes(tt.current); got != tt.wantNewer { | |
| 163 | + | t.Fatalf("Supersedes() = %t, want %t (%s)", got, tt.wantNewer, tt.description) | |
| 164 | + | } | |
| 165 | + | }) | |
| 166 | + | } | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | func TestBrokerSnapshotCloneAndFind(t *testing.T) { | |
| 170 | + | original := validBrokerSnapshot() | |
| 171 | + | original.Hosts[0].Sessions = []catalogue.RemoteCatalogSession{{ | |
| 172 | + | LifecycleID: testBrokerLifecycle(), | |
| 173 | + | Name: "work", | |
| 174 | + | State: catalogue.RemoteCatalogSessionUp, | |
| 175 | + | Tabs: []catalogue.RemoteCatalogTab{{ID: "tab-1", Index: 0, Name: "shell"}}, | |
| 176 | + | }} | |
| 177 | + | cloned := original.Clone() | |
| 178 | + | if found, ok := cloned.Find("user@arch"); !ok || found.Endpoint != "user@arch" { | |
| 179 | + | t.Fatalf("Find() = %+v, %t, want the host", found, ok) | |
| 180 | + | } | |
| 181 | + | if _, ok := cloned.Find("user@mule"); ok { | |
| 182 | + | t.Fatal("Find() must miss unknown endpoints") | |
| 183 | + | } | |
| 184 | + | cloned.Hosts[0].Sessions[0].Name = "mutated" | |
| 185 | + | cloned.Hosts[0].Sessions[0].Tabs[0].ID = "mutated" | |
| 186 | + | if original.Hosts[0].Sessions[0].Name != "work" || original.Hosts[0].Sessions[0].Tabs[0].ID != "tab-1" { | |
| 187 | + | t.Fatal("Clone() shares memory with the original") | |
| 188 | + | } | |
| 189 | + | } | |
| 190 | + | ||
| 191 | + | func TestBrokerTombstoneFencing(t *testing.T) { | |
| 192 | + | registration := testBrokerRegistration("user@arch", 4, 2) | |
| 193 | + | tombstone := BrokerHostTombstone{ | |
| 194 | + | Endpoint: "user@arch", | |
| 195 | + | Registration: registration, | |
| 196 | + | RetiredRevision: 12, | |
| 197 | + | } | |
| 198 | + | if err := tombstone.Validate(); err != nil { | |
| 199 | + | t.Fatalf("Validate() = %v", err) | |
| 200 | + | } | |
| 201 | + | tests := []struct { | |
| 202 | + | name string | |
| 203 | + | candidate domain.RemoteRegistration | |
| 204 | + | wantFence bool | |
| 205 | + | }{ | |
| 206 | + | {name: "same registration fenced", candidate: registration, wantFence: true}, | |
| 207 | + | {name: "older generation fenced", candidate: testBrokerRegistration("user@arch", 4, 1), wantFence: true}, | |
| 208 | + | {name: "fresh incarnation not fenced", candidate: testBrokerRegistration("user@arch", 5, 1), wantFence: false}, | |
| 209 | + | {name: "other endpoint not fenced", candidate: testBrokerRegistration("user@mule", 4, 2), wantFence: false}, | |
| 210 | + | } | |
| 211 | + | for _, tt := range tests { | |
| 212 | + | t.Run(tt.name, func(t *testing.T) { | |
| 213 | + | if got := tombstone.Fences(tt.candidate); got != tt.wantFence { | |
| 214 | + | t.Fatalf("Fences() = %t, want %t", got, tt.wantFence) | |
| 215 | + | } | |
| 216 | + | }) | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | invalid := []BrokerHostTombstone{ | |
| 220 | + | {Registration: registration, RetiredRevision: 1}, | |
| 221 | + | {Endpoint: "user@arch", Registration: testBrokerRegistration("user@other", 4, 2), RetiredRevision: 1}, | |
| 222 | + | {Endpoint: "user@arch", Registration: registration}, | |
| 223 | + | } | |
| 224 | + | for i, tomb := range invalid { | |
| 225 | + | if err := tomb.Validate(); err == nil { | |
| 226 | + | t.Fatalf("invalid tombstone %d accepted", i) | |
| 227 | + | } | |
| 228 | + | } | |
| 229 | + | } | |
| 230 | + | ||
| 231 | + | func TestBrokerCompletionStaleRejection(t *testing.T) { | |
| 232 | + | current := testBrokerRegistration("user@arch", 4, 2) | |
| 233 | + | tests := []struct { | |
| 234 | + | name string | |
| 235 | + | currentEpoch BrokerEpoch | |
| 236 | + | completion domain.RemoteRegistration | |
| 237 | + | completionEp BrokerEpoch | |
| 238 | + | wantStale bool | |
| 239 | + | }{ | |
| 240 | + | {name: "exact match applies", currentEpoch: 3, completion: current, completionEp: 3, wantStale: false}, | |
| 241 | + | {name: "epoch change stale", currentEpoch: 4, completion: current, completionEp: 3, wantStale: true}, | |
| 242 | + | {name: "generation bump stalls old", currentEpoch: 3, completion: testBrokerRegistration("user@arch", 4, 1), completionEp: 3, wantStale: true}, | |
| 243 | + | {name: "re-add incarnation stalls old", currentEpoch: 3, completion: testBrokerRegistration("user@arch", 5, 1), completionEp: 3, wantStale: true}, | |
| 244 | + | {name: "zero epoch stale", currentEpoch: 0, completion: current, completionEp: 3, wantStale: true}, | |
| 245 | + | {name: "zero completion epoch stale", currentEpoch: 3, completion: current, completionEp: 0, wantStale: true}, | |
| 246 | + | } | |
| 247 | + | for _, tt := range tests { | |
| 248 | + | t.Run(tt.name, func(t *testing.T) { | |
| 249 | + | if got := BrokerCompletionIsStale(current, tt.currentEpoch, tt.completion, tt.completionEp); got != tt.wantStale { | |
| 250 | + | t.Fatalf("BrokerCompletionIsStale() = %t, want %t", got, tt.wantStale) | |
| 251 | + | } | |
| 252 | + | }) | |
| 253 | + | } | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | func TestBrokerPolicyCompatibility(t *testing.T) { | |
| 257 | + | base := testBrokerPolicy() | |
| 258 | + | if err := base.Validate(); err != nil { | |
| 259 | + | t.Fatalf("Validate() = %v", err) | |
| 260 | + | } | |
| 261 | + | if !base.Compatible(base) { | |
| 262 | + | t.Fatal("identical policy must be compatible") | |
| 263 | + | } | |
| 264 | + | conflicts := map[string]BrokerPolicy{ | |
| 265 | + | "protocol version": withBrokerPolicy(base, func(p *BrokerPolicy) { p.ProtocolVersion++ }), | |
| 266 | + | "catalog schema": withBrokerPolicy(base, func(p *BrokerPolicy) { p.CatalogSchemaVersion++ }), | |
| 267 | + | "environment": withBrokerPolicy(base, func(p *BrokerPolicy) { p.EnvironmentPolicy = protocol.EnvironmentPolicyClientOwned }), | |
| 268 | + | "transport": withBrokerPolicy(base, func(p *BrokerPolicy) { p.Transport = "stdio" }), | |
| 269 | + | } | |
| 270 | + | for name, conflict := range conflicts { | |
| 271 | + | if base.Compatible(conflict) { | |
| 272 | + | t.Fatalf("conflicting policy %q must be rejected, never merged", name) | |
| 273 | + | } | |
| 274 | + | } | |
| 275 | + | invalid := []BrokerPolicy{ | |
| 276 | + | {}, | |
| 277 | + | {ProtocolVersion: protocol.Version}, | |
| 278 | + | {ProtocolVersion: protocol.Version, CatalogSchemaVersion: catalogue.RemoteCatalogSchemaVersion}, | |
| 279 | + | {ProtocolVersion: protocol.Version, CatalogSchemaVersion: catalogue.RemoteCatalogSchemaVersion, EnvironmentPolicy: protocol.EnvironmentPolicy(9), Transport: "quic"}, | |
| 280 | + | {ProtocolVersion: protocol.Version, CatalogSchemaVersion: catalogue.RemoteCatalogSchemaVersion, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned}, | |
| 281 | + | } | |
| 282 | + | for i, policy := range invalid { | |
| 283 | + | if err := policy.Validate(); err == nil { | |
| 284 | + | t.Fatalf("invalid policy %d accepted", i) | |
| 285 | + | } | |
| 286 | + | } | |
| 287 | + | } | |
| 288 | + | ||
| 289 | + | func TestBrokerOpenStreamRequestValidate(t *testing.T) { | |
| 290 | + | valid := func() BrokerOpenStreamRequest { | |
| 291 | + | return BrokerOpenStreamRequest{ | |
| 292 | + | Connection: BrokerConnectionID{1}, | |
| 293 | + | Stream: 2, | |
| 294 | + | Endpoint: "user@arch", | |
| 295 | + | Registration: testBrokerRegistration("user@arch", 4, 2), | |
| 296 | + | Target: testBrokerTarget(), | |
| 297 | + | Env: []string{"TERM=xterm-256color"}, | |
| 298 | + | Policy: testBrokerPolicy(), | |
| 299 | + | } | |
| 300 | + | } | |
| 301 | + | if err := valid().Validate(); err != nil { | |
| 302 | + | t.Fatalf("Validate() = %v", err) | |
| 303 | + | } | |
| 304 | + | tests := []struct { | |
| 305 | + | name string | |
| 306 | + | mutate func(*BrokerOpenStreamRequest) | |
| 307 | + | wantErr string | |
| 308 | + | }{ | |
| 309 | + | {name: "zero connection", mutate: func(r *BrokerOpenStreamRequest) { r.Connection = BrokerConnectionID{} }}, | |
| 310 | + | {name: "zero stream", mutate: func(r *BrokerOpenStreamRequest) { r.Stream = 0 }}, | |
| 311 | + | {name: "empty endpoint", mutate: func(r *BrokerOpenStreamRequest) { r.Endpoint = "" }}, | |
| 312 | + | {name: "endpoint registration mismatch", mutate: func(r *BrokerOpenStreamRequest) { r.Registration = testBrokerRegistration("user@other", 4, 2) }}, | |
| 313 | + | {name: "zero target lifecycle", mutate: func(r *BrokerOpenStreamRequest) { | |
| 314 | + | r.Target = protocol.ExactSessionTarget{SessionName: "work"} | |
| 315 | + | }}, | |
| 316 | + | {name: "bad session name", mutate: func(r *BrokerOpenStreamRequest) { | |
| 317 | + | r.Target = protocol.ExactSessionTarget{LifecycleID: testBrokerLifecycle(), SessionName: "bad name!"} | |
| 318 | + | }}, | |
| 319 | + | {name: "too many env entries", mutate: func(r *BrokerOpenStreamRequest) { | |
| 320 | + | r.Env = make([]string, BrokerMaxEnvEntries+1) | |
| 321 | + | }}, | |
| 322 | + | {name: "oversize env entry", mutate: func(r *BrokerOpenStreamRequest) { | |
| 323 | + | r.Env = []string{strings.Repeat("A", BrokerMaxEnvEntryBytes+1)} | |
| 324 | + | }}, | |
| 325 | + | {name: "invalid env encoding", mutate: func(r *BrokerOpenStreamRequest) { | |
| 326 | + | r.Env = []string{"TERM=\xff"} | |
| 327 | + | }}, | |
| 328 | + | {name: "invalid policy", mutate: func(r *BrokerOpenStreamRequest) { r.Policy = BrokerPolicy{} }}, | |
| 329 | + | } | |
| 330 | + | for _, tt := range tests { | |
| 331 | + | t.Run(tt.name, func(t *testing.T) { | |
| 332 | + | request := valid() | |
| 333 | + | tt.mutate(&request) | |
| 334 | + | if err := request.Validate(); err == nil { | |
| 335 | + | t.Fatal("invalid open-stream request accepted") | |
| 336 | + | } | |
| 337 | + | }) | |
| 338 | + | } | |
| 339 | + | } | |
| 340 | + | ||
| 341 | + | func TestBrokerMutationOutcomeUncertainty(t *testing.T) { | |
| 342 | + | for _, outcome := range []BrokerMutationOutcome{BrokerOutcomeOK, BrokerOutcomeFailed, BrokerOutcomeUnknown} { | |
| 343 | + | if err := outcome.Validate(); err != nil { | |
| 344 | + | t.Fatalf("Validate(%v) = %v", outcome, err) | |
| 345 | + | } | |
| 346 | + | } | |
| 347 | + | if err := BrokerMutationOutcome(0).Validate(); err == nil { | |
| 348 | + | t.Fatal("zero mutation outcome accepted") | |
| 349 | + | } | |
| 350 | + | if BrokerOutcomeOK.String() != "ok" || BrokerOutcomeUnknown.String() != "outcome_unknown" { | |
| 351 | + | t.Fatal("mutation outcome tokens changed") | |
| 352 | + | } | |
| 353 | + | ||
| 354 | + | valid := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeFailed, Text: "dial refused"} | |
| 355 | + | if err := valid.Validate(); err != nil { | |
| 356 | + | t.Fatalf("Validate() = %v", err) | |
| 357 | + | } | |
| 358 | + | unknown := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeUnknown, Text: "lost after commit"} | |
| 359 | + | if err := unknown.Validate(); err != nil { | |
| 360 | + | t.Fatalf("outcome-unknown result must stay valid: %v", err) | |
| 361 | + | } | |
| 362 | + | okWithText := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeOK, Text: "extra"} | |
| 363 | + | if err := okWithText.Validate(); err == nil { | |
| 364 | + | t.Fatal("successful result must not carry text") | |
| 365 | + | } | |
| 366 | + | oversize := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeFailed, Text: strings.Repeat("e", BrokerMaxErrorBytes+1)} | |
| 367 | + | if err := oversize.Validate(); err == nil { | |
| 368 | + | t.Fatal("oversize result text accepted") | |
| 369 | + | } | |
| 370 | + | unsafe := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeFailed, Text: "dial\x1b[31m refused"} | |
| 371 | + | if err := unsafe.Validate(); err == nil { | |
| 372 | + | t.Fatal("terminal control sequence in operation result accepted") | |
| 373 | + | } | |
| 374 | + | } | |
| 375 | + | ||
| 376 | + | func TestBrokerErrorTerminalOutcomes(t *testing.T) { | |
| 377 | + | tests := []struct { | |
| 378 | + | code BrokerErrorCode | |
| 379 | + | wantTerminal bool | |
| 380 | + | }{ | |
| 381 | + | {code: BrokerErrorUnavailable, wantTerminal: false}, | |
| 382 | + | {code: BrokerErrorIncompatible, wantTerminal: false}, | |
| 383 | + | {code: BrokerErrorTimeout, wantTerminal: false}, | |
| 384 | + | {code: BrokerErrorCancelled, wantTerminal: false}, | |
| 385 | + | {code: BrokerErrorOutcomeUnknown, wantTerminal: false}, | |
| 386 | + | {code: BrokerErrorAttachmentLost, wantTerminal: false}, | |
| 387 | + | {code: BrokerErrorStaleEpoch, wantTerminal: false}, | |
| 388 | + | {code: BrokerErrorConflictingPolicy, wantTerminal: false}, | |
| 389 | + | {code: BrokerErrorExplicitExit, wantTerminal: true}, | |
| 390 | + | {code: BrokerErrorFatalTerminal, wantTerminal: true}, | |
| 391 | + | } | |
| 392 | + | for _, tt := range tests { | |
| 393 | + | t.Run(tt.code.String(), func(t *testing.T) { | |
| 394 | + | failure := BrokerError{Code: tt.code, Text: "visible typed error"} | |
| 395 | + | if err := failure.Validate(); err != nil { | |
| 396 | + | t.Fatalf("Validate() = %v", err) | |
| 397 | + | } | |
| 398 | + | if got := failure.Terminal(); got != tt.wantTerminal { | |
| 399 | + | t.Fatalf("Terminal() = %t, want %t", got, tt.wantTerminal) | |
| 400 | + | } | |
| 401 | + | if failure.Error() == "" { | |
| 402 | + | t.Fatal("Error() must render a message") | |
| 403 | + | } | |
| 404 | + | }) | |
| 405 | + | } | |
| 406 | + | if err := (BrokerError{Code: BrokerErrorCode(0)}).Validate(); err == nil { | |
| 407 | + | t.Fatal("zero broker error code accepted") | |
| 408 | + | } | |
| 409 | + | oversize := BrokerError{Code: BrokerErrorTimeout, Text: strings.Repeat("e", BrokerMaxErrorBytes+1)} | |
| 410 | + | if err := oversize.Validate(); err == nil { | |
| 411 | + | t.Fatal("oversize broker error text accepted") | |
| 412 | + | } | |
| 413 | + | unsafe := BrokerError{Code: BrokerErrorTimeout, Text: "timeout\u202erorrE"} | |
| 414 | + | if err := unsafe.Validate(); err == nil { | |
| 415 | + | t.Fatal("bidirectional control in broker error accepted") | |
| 416 | + | } | |
| 417 | + | } | |
| 418 | + | ||
| 419 | + | func TestBrokerIdentityScopesStayDistinct(t *testing.T) { | |
| 420 | + | // Every identity level validates independently and zero values never | |
| 421 | + | // identify anything: connection, stream, operation, daemon incarnation, | |
| 422 | + | // registration, session lifecycle, and broker epoch are distinct types. | |
| 423 | + | if err := (BrokerConnectionID{}).Validate(); err == nil { | |
| 424 | + | t.Fatal("zero connection ID accepted") | |
| 425 | + | } | |
| 426 | + | if err := BrokerStreamID(0).Validate(); err == nil { | |
| 427 | + | t.Fatal("zero stream ID accepted") | |
| 428 | + | } | |
| 429 | + | if err := (BrokerOperationID{}).Validate(); err == nil { | |
| 430 | + | t.Fatal("zero operation ID accepted") | |
| 431 | + | } | |
| 432 | + | if err := (BrokerDaemonIncarnation{}).Validate(); err == nil { | |
| 433 | + | t.Fatal("zero daemon incarnation accepted") | |
| 434 | + | } | |
| 435 | + | if err := (domain.RemoteRegistration{}).Validate(); err == nil { | |
| 436 | + | t.Fatal("zero host registration accepted") | |
| 437 | + | } | |
| 438 | + | if err := (protocol.ExactSessionTarget{}).Validate(); err == nil { | |
| 439 | + | t.Fatal("zero session lifecycle target accepted") | |
| 440 | + | } | |
| 441 | + | if err := BrokerDaemonIdentity("").Validate(); err == nil { | |
| 442 | + | t.Fatal("empty daemon identity accepted") | |
| 443 | + | } | |
| 444 | + | if err := (BrokerStreamLost{}).Validate(); err == nil { | |
| 445 | + | t.Fatal("zero stream loss accepted") | |
| 446 | + | } | |
| 447 | + | loss := BrokerStreamLost{ | |
| 448 | + | Connection: BrokerConnectionID{1}, | |
| 449 | + | Stream: 2, | |
| 450 | + | Epoch: 3, | |
| 451 | + | Cause: domain.RemoteFailureTimeout, | |
| 452 | + | } | |
| 453 | + | if err := loss.Validate(); err != nil { | |
| 454 | + | t.Fatalf("Validate() = %v", err) | |
| 455 | + | } | |
| 456 | + | } | |
| 457 | + | ||
| 458 | + | func withBrokerPolicy(base BrokerPolicy, mutate func(*BrokerPolicy)) BrokerPolicy { | |
| 459 | + | mutate(&base) | |
| 460 | + | return base | |
| 461 | + | } | |
| 462 | + | ||
| 463 | + | func TestBrokerInterfacesArePorts(t *testing.T) { | |
| 464 | + | // Broker, client, and daemon cross-layer contracts are ports: the | |
| 465 | + | // seams must stay interfaces so Mockery can generate mocks and | |
| 466 | + | // adapters can implement them without importing use cases. | |
| 467 | + | for name, iface := range map[string]reflect.Type{ | |
| 468 | + | "BrokerService": reflect.TypeOf((*BrokerService)(nil)).Elem(), | |
| 469 | + | "BrokerSnapshotStore": reflect.TypeOf((*BrokerSnapshotStore)(nil)).Elem(), | |
| 470 | + | "BrokerPhysicalConnection": reflect.TypeOf((*BrokerPhysicalConnection)(nil)).Elem(), | |
| 471 | + | "BrokerEndpointConnector": reflect.TypeOf((*BrokerEndpointConnector)(nil)).Elem(), | |
| 472 | + | "BrokerListener": reflect.TypeOf((*BrokerListener)(nil)).Elem(), | |
| 473 | + | "BrokerSubscription": reflect.TypeOf((*BrokerSubscription)(nil)).Elem(), | |
| 474 | + | } { | |
| 475 | + | if iface.Kind() != reflect.Interface { | |
| 476 | + | t.Fatalf("%s must stay an interface, got %v", name, iface.Kind()) | |
| 477 | + | } | |
| 478 | + | } | |
| 479 | + | } | |
| @@ -0,0 +1,479 @@ | |||||
| 1 | + | package ports | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "reflect" | |||
| 5 | + | "strings" | |||
| 6 | + | "testing" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/vev/internal/domain" | |||
| 9 | + | "github.com/bnema/vev/internal/protocol" | |||
| 10 | + | "github.com/bnema/vev/internal/protocol/catalogue" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | func testBrokerRegistration(endpoint string, incarnation byte, generation domain.RemoteGeneration) domain.RemoteRegistration { | |||
| 14 | + | return domain.RemoteRegistration{ | |||
| 15 | + | Endpoint: endpoint, | |||
| 16 | + | Incarnation: [16]byte{incarnation, 7, 9}, | |||
| 17 | + | Generation: generation, | |||
| 18 | + | } | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | func testBrokerLifecycle() domain.SessionLifecycleID { | |||
| 22 | + | return domain.SessionLifecycleID{1, 2, 3} | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | func testBrokerTarget() protocol.ExactSessionTarget { | |||
| 26 | + | return protocol.ExactSessionTarget{LifecycleID: testBrokerLifecycle(), SessionName: "work"} | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | func testBrokerPolicy() BrokerPolicy { | |||
| 30 | + | return BrokerPolicy{ | |||
| 31 | + | ProtocolVersion: protocol.Version, | |||
| 32 | + | CatalogSchemaVersion: catalogue.RemoteCatalogSchemaVersion, | |||
| 33 | + | EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned, | |||
| 34 | + | Transport: "quic", | |||
| 35 | + | } | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | func testBrokerHost(endpoint string) RemoteHostSnapshot { | |||
| 39 | + | return RemoteHostSnapshot{ | |||
| 40 | + | Endpoint: endpoint, | |||
| 41 | + | DisplayOrigin: endpoint, | |||
| 42 | + | Registration: testBrokerRegistration(endpoint, 1, 1), | |||
| 43 | + | Availability: domain.RemoteAvailabilityReachable, | |||
| 44 | + | } | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | func validBrokerSnapshot() BrokerSnapshot { | |||
| 48 | + | return BrokerSnapshot{ | |||
| 49 | + | Epoch: 3, | |||
| 50 | + | Revision: 9, | |||
| 51 | + | Hosts: []RemoteHostSnapshot{testBrokerHost("user@arch")}, | |||
| 52 | + | } | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | func TestBrokerSnapshotValidateBounds(t *testing.T) { | |||
| 56 | + | tests := []struct { | |||
| 57 | + | name string | |||
| 58 | + | mutate func(*BrokerSnapshot) | |||
| 59 | + | wantErr bool | |||
| 60 | + | }{ | |||
| 61 | + | {name: "valid", mutate: func(*BrokerSnapshot) {}, wantErr: false}, | |||
| 62 | + | {name: "zero epoch", mutate: func(s *BrokerSnapshot) { s.Epoch = 0 }, wantErr: true}, | |||
| 63 | + | {name: "zero revision", mutate: func(s *BrokerSnapshot) { s.Revision = 0 }, wantErr: true}, | |||
| 64 | + | {name: "too many hosts", mutate: func(s *BrokerSnapshot) { | |||
| 65 | + | s.Hosts = make([]RemoteHostSnapshot, BrokerMaxHosts+1) | |||
| 66 | + | for i := range s.Hosts { | |||
| 67 | + | s.Hosts[i] = testBrokerHost("user@arch") | |||
| 68 | + | s.Hosts[i].Registration = testBrokerRegistration("user@arch", 1, 1) | |||
| 69 | + | } | |||
| 70 | + | }, wantErr: true}, | |||
| 71 | + | {name: "duplicate host", mutate: func(s *BrokerSnapshot) { | |||
| 72 | + | s.Hosts = append(s.Hosts, testBrokerHost("user@arch")) | |||
| 73 | + | }, wantErr: true}, | |||
| 74 | + | {name: "endpoint registration mismatch", mutate: func(s *BrokerSnapshot) { | |||
| 75 | + | s.Hosts[0].Registration = testBrokerRegistration("user@other", 1, 1) | |||
| 76 | + | }, wantErr: true}, | |||
| 77 | + | {name: "zero registration incarnation", mutate: func(s *BrokerSnapshot) { | |||
| 78 | + | s.Hosts[0].Registration = domain.RemoteRegistration{Endpoint: "user@arch", Generation: 1} | |||
| 79 | + | }, wantErr: true}, | |||
| 80 | + | {name: "live host as tombstone", mutate: func(s *BrokerSnapshot) { | |||
| 81 | + | s.Removed = []BrokerHostTombstone{{ | |||
| 82 | + | Endpoint: "user@arch", | |||
| 83 | + | Registration: testBrokerRegistration("user@arch", 1, 1), | |||
| 84 | + | RetiredRevision: 9, | |||
| 85 | + | }} | |||
| 86 | + | }, wantErr: true}, | |||
| 87 | + | {name: "too many tombstones", mutate: func(s *BrokerSnapshot) { | |||
| 88 | + | s.Removed = make([]BrokerHostTombstone, BrokerMaxTombstones+1) | |||
| 89 | + | for i := range s.Removed { | |||
| 90 | + | s.Removed[i] = BrokerHostTombstone{ | |||
| 91 | + | Endpoint: "user@gone", | |||
| 92 | + | Registration: testBrokerRegistration("user@gone", 1, 1), | |||
| 93 | + | RetiredRevision: 9, | |||
| 94 | + | } | |||
| 95 | + | } | |||
| 96 | + | }, wantErr: true}, | |||
| 97 | + | } | |||
| 98 | + | for _, tt := range tests { | |||
| 99 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 100 | + | snapshot := validBrokerSnapshot() | |||
| 101 | + | tt.mutate(&snapshot) | |||
| 102 | + | if err := snapshot.Validate(); (err != nil) != tt.wantErr { | |||
| 103 | + | t.Fatalf("Validate() = %v, wantErr %t", err, tt.wantErr) | |||
| 104 | + | } | |||
| 105 | + | }) | |||
| 106 | + | } | |||
| 107 | + | } | |||
| 108 | + | ||||
| 109 | + | func TestBrokerSnapshotEpochRevisionOrdering(t *testing.T) { | |||
| 110 | + | tests := []struct { | |||
| 111 | + | name string | |||
| 112 | + | current BrokerSnapshot | |||
| 113 | + | candidate BrokerSnapshot | |||
| 114 | + | wantNewer bool | |||
| 115 | + | description string | |||
| 116 | + | }{ | |||
| 117 | + | { | |||
| 118 | + | name: "higher revision wins in epoch", | |||
| 119 | + | current: BrokerSnapshot{Epoch: 3, Revision: 9}, | |||
| 120 | + | candidate: BrokerSnapshot{Epoch: 3, Revision: 10}, | |||
| 121 | + | wantNewer: true, | |||
| 122 | + | description: "monotonic revision within an epoch", | |||
| 123 | + | }, | |||
| 124 | + | { | |||
| 125 | + | name: "lower revision loses in epoch", | |||
| 126 | + | current: BrokerSnapshot{Epoch: 3, Revision: 10}, | |||
| 127 | + | candidate: BrokerSnapshot{Epoch: 3, Revision: 9}, | |||
| 128 | + | wantNewer: false, | |||
| 129 | + | description: "stale revision never applies", | |||
| 130 | + | }, | |||
| 131 | + | { | |||
| 132 | + | name: "equal revision loses", | |||
| 133 | + | current: BrokerSnapshot{Epoch: 3, Revision: 9}, | |||
| 134 | + | candidate: BrokerSnapshot{Epoch: 3, Revision: 9}, | |||
| 135 | + | wantNewer: false, | |||
| 136 | + | description: "replay of the same revision is not newer", | |||
| 137 | + | }, | |||
| 138 | + | { | |||
| 139 | + | name: "epoch change forces resync", | |||
| 140 | + | current: BrokerSnapshot{Epoch: 3, Revision: 99}, | |||
| 141 | + | candidate: BrokerSnapshot{Epoch: 4, Revision: 1}, | |||
| 142 | + | wantNewer: true, | |||
| 143 | + | description: "any valid epoch change supersedes regardless of revision", | |||
| 144 | + | }, | |||
| 145 | + | { | |||
| 146 | + | name: "lower epoch still forces resync", | |||
| 147 | + | current: BrokerSnapshot{Epoch: 9, Revision: 1}, | |||
| 148 | + | candidate: BrokerSnapshot{Epoch: 4, Revision: 50}, | |||
| 149 | + | wantNewer: true, | |||
| 150 | + | description: "epochs are fresh per process, never numerically ordered", | |||
| 151 | + | }, | |||
| 152 | + | { | |||
| 153 | + | name: "zero epoch never supersedes", | |||
| 154 | + | current: BrokerSnapshot{Epoch: 3, Revision: 9}, | |||
| 155 | + | candidate: BrokerSnapshot{}, | |||
| 156 | + | wantNewer: false, | |||
| 157 | + | description: "invalid snapshots never apply", | |||
| 158 | + | }, | |||
| 159 | + | } | |||
| 160 | + | for _, tt := range tests { | |||
| 161 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 162 | + | if got := tt.candidate.Supersedes(tt.current); got != tt.wantNewer { | |||
| 163 | + | t.Fatalf("Supersedes() = %t, want %t (%s)", got, tt.wantNewer, tt.description) | |||
| 164 | + | } | |||
| 165 | + | }) | |||
| 166 | + | } | |||
| 167 | + | } | |||
| 168 | + | ||||
| 169 | + | func TestBrokerSnapshotCloneAndFind(t *testing.T) { | |||
| 170 | + | original := validBrokerSnapshot() | |||
| 171 | + | original.Hosts[0].Sessions = []catalogue.RemoteCatalogSession{{ | |||
| 172 | + | LifecycleID: testBrokerLifecycle(), | |||
| 173 | + | Name: "work", | |||
| 174 | + | State: catalogue.RemoteCatalogSessionUp, | |||
| 175 | + | Tabs: []catalogue.RemoteCatalogTab{{ID: "tab-1", Index: 0, Name: "shell"}}, | |||
| 176 | + | }} | |||
| 177 | + | cloned := original.Clone() | |||
| 178 | + | if found, ok := cloned.Find("user@arch"); !ok || found.Endpoint != "user@arch" { | |||
| 179 | + | t.Fatalf("Find() = %+v, %t, want the host", found, ok) | |||
| 180 | + | } | |||
| 181 | + | if _, ok := cloned.Find("user@mule"); ok { | |||
| 182 | + | t.Fatal("Find() must miss unknown endpoints") | |||
| 183 | + | } | |||
| 184 | + | cloned.Hosts[0].Sessions[0].Name = "mutated" | |||
| 185 | + | cloned.Hosts[0].Sessions[0].Tabs[0].ID = "mutated" | |||
| 186 | + | if original.Hosts[0].Sessions[0].Name != "work" || original.Hosts[0].Sessions[0].Tabs[0].ID != "tab-1" { | |||
| 187 | + | t.Fatal("Clone() shares memory with the original") | |||
| 188 | + | } | |||
| 189 | + | } | |||
| 190 | + | ||||
| 191 | + | func TestBrokerTombstoneFencing(t *testing.T) { | |||
| 192 | + | registration := testBrokerRegistration("user@arch", 4, 2) | |||
| 193 | + | tombstone := BrokerHostTombstone{ | |||
| 194 | + | Endpoint: "user@arch", | |||
| 195 | + | Registration: registration, | |||
| 196 | + | RetiredRevision: 12, | |||
| 197 | + | } | |||
| 198 | + | if err := tombstone.Validate(); err != nil { | |||
| 199 | + | t.Fatalf("Validate() = %v", err) | |||
| 200 | + | } | |||
| 201 | + | tests := []struct { | |||
| 202 | + | name string | |||
| 203 | + | candidate domain.RemoteRegistration | |||
| 204 | + | wantFence bool | |||
| 205 | + | }{ | |||
| 206 | + | {name: "same registration fenced", candidate: registration, wantFence: true}, | |||
| 207 | + | {name: "older generation fenced", candidate: testBrokerRegistration("user@arch", 4, 1), wantFence: true}, | |||
| 208 | + | {name: "fresh incarnation not fenced", candidate: testBrokerRegistration("user@arch", 5, 1), wantFence: false}, | |||
| 209 | + | {name: "other endpoint not fenced", candidate: testBrokerRegistration("user@mule", 4, 2), wantFence: false}, | |||
| 210 | + | } | |||
| 211 | + | for _, tt := range tests { | |||
| 212 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 213 | + | if got := tombstone.Fences(tt.candidate); got != tt.wantFence { | |||
| 214 | + | t.Fatalf("Fences() = %t, want %t", got, tt.wantFence) | |||
| 215 | + | } | |||
| 216 | + | }) | |||
| 217 | + | } | |||
| 218 | + | ||||
| 219 | + | invalid := []BrokerHostTombstone{ | |||
| 220 | + | {Registration: registration, RetiredRevision: 1}, | |||
| 221 | + | {Endpoint: "user@arch", Registration: testBrokerRegistration("user@other", 4, 2), RetiredRevision: 1}, | |||
| 222 | + | {Endpoint: "user@arch", Registration: registration}, | |||
| 223 | + | } | |||
| 224 | + | for i, tomb := range invalid { | |||
| 225 | + | if err := tomb.Validate(); err == nil { | |||
| 226 | + | t.Fatalf("invalid tombstone %d accepted", i) | |||
| 227 | + | } | |||
| 228 | + | } | |||
| 229 | + | } | |||
| 230 | + | ||||
| 231 | + | func TestBrokerCompletionStaleRejection(t *testing.T) { | |||
| 232 | + | current := testBrokerRegistration("user@arch", 4, 2) | |||
| 233 | + | tests := []struct { | |||
| 234 | + | name string | |||
| 235 | + | currentEpoch BrokerEpoch | |||
| 236 | + | completion domain.RemoteRegistration | |||
| 237 | + | completionEp BrokerEpoch | |||
| 238 | + | wantStale bool | |||
| 239 | + | }{ | |||
| 240 | + | {name: "exact match applies", currentEpoch: 3, completion: current, completionEp: 3, wantStale: false}, | |||
| 241 | + | {name: "epoch change stale", currentEpoch: 4, completion: current, completionEp: 3, wantStale: true}, | |||
| 242 | + | {name: "generation bump stalls old", currentEpoch: 3, completion: testBrokerRegistration("user@arch", 4, 1), completionEp: 3, wantStale: true}, | |||
| 243 | + | {name: "re-add incarnation stalls old", currentEpoch: 3, completion: testBrokerRegistration("user@arch", 5, 1), completionEp: 3, wantStale: true}, | |||
| 244 | + | {name: "zero epoch stale", currentEpoch: 0, completion: current, completionEp: 3, wantStale: true}, | |||
| 245 | + | {name: "zero completion epoch stale", currentEpoch: 3, completion: current, completionEp: 0, wantStale: true}, | |||
| 246 | + | } | |||
| 247 | + | for _, tt := range tests { | |||
| 248 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 249 | + | if got := BrokerCompletionIsStale(current, tt.currentEpoch, tt.completion, tt.completionEp); got != tt.wantStale { | |||
| 250 | + | t.Fatalf("BrokerCompletionIsStale() = %t, want %t", got, tt.wantStale) | |||
| 251 | + | } | |||
| 252 | + | }) | |||
| 253 | + | } | |||
| 254 | + | } | |||
| 255 | + | ||||
| 256 | + | func TestBrokerPolicyCompatibility(t *testing.T) { | |||
| 257 | + | base := testBrokerPolicy() | |||
| 258 | + | if err := base.Validate(); err != nil { | |||
| 259 | + | t.Fatalf("Validate() = %v", err) | |||
| 260 | + | } | |||
| 261 | + | if !base.Compatible(base) { | |||
| 262 | + | t.Fatal("identical policy must be compatible") | |||
| 263 | + | } | |||
| 264 | + | conflicts := map[string]BrokerPolicy{ | |||
| 265 | + | "protocol version": withBrokerPolicy(base, func(p *BrokerPolicy) { p.ProtocolVersion++ }), | |||
| 266 | + | "catalog schema": withBrokerPolicy(base, func(p *BrokerPolicy) { p.CatalogSchemaVersion++ }), | |||
| 267 | + | "environment": withBrokerPolicy(base, func(p *BrokerPolicy) { p.EnvironmentPolicy = protocol.EnvironmentPolicyClientOwned }), | |||
| 268 | + | "transport": withBrokerPolicy(base, func(p *BrokerPolicy) { p.Transport = "stdio" }), | |||
| 269 | + | } | |||
| 270 | + | for name, conflict := range conflicts { | |||
| 271 | + | if base.Compatible(conflict) { | |||
| 272 | + | t.Fatalf("conflicting policy %q must be rejected, never merged", name) | |||
| 273 | + | } | |||
| 274 | + | } | |||
| 275 | + | invalid := []BrokerPolicy{ | |||
| 276 | + | {}, | |||
| 277 | + | {ProtocolVersion: protocol.Version}, | |||
| 278 | + | {ProtocolVersion: protocol.Version, CatalogSchemaVersion: catalogue.RemoteCatalogSchemaVersion}, | |||
| 279 | + | {ProtocolVersion: protocol.Version, CatalogSchemaVersion: catalogue.RemoteCatalogSchemaVersion, EnvironmentPolicy: protocol.EnvironmentPolicy(9), Transport: "quic"}, | |||
| 280 | + | {ProtocolVersion: protocol.Version, CatalogSchemaVersion: catalogue.RemoteCatalogSchemaVersion, EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned}, | |||
| 281 | + | } | |||
| 282 | + | for i, policy := range invalid { | |||
| 283 | + | if err := policy.Validate(); err == nil { | |||
| 284 | + | t.Fatalf("invalid policy %d accepted", i) | |||
| 285 | + | } | |||
| 286 | + | } | |||
| 287 | + | } | |||
| 288 | + | ||||
| 289 | + | func TestBrokerOpenStreamRequestValidate(t *testing.T) { | |||
| 290 | + | valid := func() BrokerOpenStreamRequest { | |||
| 291 | + | return BrokerOpenStreamRequest{ | |||
| 292 | + | Connection: BrokerConnectionID{1}, | |||
| 293 | + | Stream: 2, | |||
| 294 | + | Endpoint: "user@arch", | |||
| 295 | + | Registration: testBrokerRegistration("user@arch", 4, 2), | |||
| 296 | + | Target: testBrokerTarget(), | |||
| 297 | + | Env: []string{"TERM=xterm-256color"}, | |||
| 298 | + | Policy: testBrokerPolicy(), | |||
| 299 | + | } | |||
| 300 | + | } | |||
| 301 | + | if err := valid().Validate(); err != nil { | |||
| 302 | + | t.Fatalf("Validate() = %v", err) | |||
| 303 | + | } | |||
| 304 | + | tests := []struct { | |||
| 305 | + | name string | |||
| 306 | + | mutate func(*BrokerOpenStreamRequest) | |||
| 307 | + | wantErr string | |||
| 308 | + | }{ | |||
| 309 | + | {name: "zero connection", mutate: func(r *BrokerOpenStreamRequest) { r.Connection = BrokerConnectionID{} }}, | |||
| 310 | + | {name: "zero stream", mutate: func(r *BrokerOpenStreamRequest) { r.Stream = 0 }}, | |||
| 311 | + | {name: "empty endpoint", mutate: func(r *BrokerOpenStreamRequest) { r.Endpoint = "" }}, | |||
| 312 | + | {name: "endpoint registration mismatch", mutate: func(r *BrokerOpenStreamRequest) { r.Registration = testBrokerRegistration("user@other", 4, 2) }}, | |||
| 313 | + | {name: "zero target lifecycle", mutate: func(r *BrokerOpenStreamRequest) { | |||
| 314 | + | r.Target = protocol.ExactSessionTarget{SessionName: "work"} | |||
| 315 | + | }}, | |||
| 316 | + | {name: "bad session name", mutate: func(r *BrokerOpenStreamRequest) { | |||
| 317 | + | r.Target = protocol.ExactSessionTarget{LifecycleID: testBrokerLifecycle(), SessionName: "bad name!"} | |||
| 318 | + | }}, | |||
| 319 | + | {name: "too many env entries", mutate: func(r *BrokerOpenStreamRequest) { | |||
| 320 | + | r.Env = make([]string, BrokerMaxEnvEntries+1) | |||
| 321 | + | }}, | |||
| 322 | + | {name: "oversize env entry", mutate: func(r *BrokerOpenStreamRequest) { | |||
| 323 | + | r.Env = []string{strings.Repeat("A", BrokerMaxEnvEntryBytes+1)} | |||
| 324 | + | }}, | |||
| 325 | + | {name: "invalid env encoding", mutate: func(r *BrokerOpenStreamRequest) { | |||
| 326 | + | r.Env = []string{"TERM=\xff"} | |||
| 327 | + | }}, | |||
| 328 | + | {name: "invalid policy", mutate: func(r *BrokerOpenStreamRequest) { r.Policy = BrokerPolicy{} }}, | |||
| 329 | + | } | |||
| 330 | + | for _, tt := range tests { | |||
| 331 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 332 | + | request := valid() | |||
| 333 | + | tt.mutate(&request) | |||
| 334 | + | if err := request.Validate(); err == nil { | |||
| 335 | + | t.Fatal("invalid open-stream request accepted") | |||
| 336 | + | } | |||
| 337 | + | }) | |||
| 338 | + | } | |||
| 339 | + | } | |||
| 340 | + | ||||
| 341 | + | func TestBrokerMutationOutcomeUncertainty(t *testing.T) { | |||
| 342 | + | for _, outcome := range []BrokerMutationOutcome{BrokerOutcomeOK, BrokerOutcomeFailed, BrokerOutcomeUnknown} { | |||
| 343 | + | if err := outcome.Validate(); err != nil { | |||
| 344 | + | t.Fatalf("Validate(%v) = %v", outcome, err) | |||
| 345 | + | } | |||
| 346 | + | } | |||
| 347 | + | if err := BrokerMutationOutcome(0).Validate(); err == nil { | |||
| 348 | + | t.Fatal("zero mutation outcome accepted") | |||
| 349 | + | } | |||
| 350 | + | if BrokerOutcomeOK.String() != "ok" || BrokerOutcomeUnknown.String() != "outcome_unknown" { | |||
| 351 | + | t.Fatal("mutation outcome tokens changed") | |||
| 352 | + | } | |||
| 353 | + | ||||
| 354 | + | valid := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeFailed, Text: "dial refused"} | |||
| 355 | + | if err := valid.Validate(); err != nil { | |||
| 356 | + | t.Fatalf("Validate() = %v", err) | |||
| 357 | + | } | |||
| 358 | + | unknown := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeUnknown, Text: "lost after commit"} | |||
| 359 | + | if err := unknown.Validate(); err != nil { | |||
| 360 | + | t.Fatalf("outcome-unknown result must stay valid: %v", err) | |||
| 361 | + | } | |||
| 362 | + | okWithText := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeOK, Text: "extra"} | |||
| 363 | + | if err := okWithText.Validate(); err == nil { | |||
| 364 | + | t.Fatal("successful result must not carry text") | |||
| 365 | + | } | |||
| 366 | + | oversize := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeFailed, Text: strings.Repeat("e", BrokerMaxErrorBytes+1)} | |||
| 367 | + | if err := oversize.Validate(); err == nil { | |||
| 368 | + | t.Fatal("oversize result text accepted") | |||
| 369 | + | } | |||
| 370 | + | unsafe := BrokerOperationResult{Operation: BrokerOperationID{1}, Outcome: BrokerOutcomeFailed, Text: "dial\x1b[31m refused"} | |||
| 371 | + | if err := unsafe.Validate(); err == nil { | |||
| 372 | + | t.Fatal("terminal control sequence in operation result accepted") | |||
| 373 | + | } | |||
| 374 | + | } | |||
| 375 | + | ||||
| 376 | + | func TestBrokerErrorTerminalOutcomes(t *testing.T) { | |||
| 377 | + | tests := []struct { | |||
| 378 | + | code BrokerErrorCode | |||
| 379 | + | wantTerminal bool | |||
| 380 | + | }{ | |||
| 381 | + | {code: BrokerErrorUnavailable, wantTerminal: false}, | |||
| 382 | + | {code: BrokerErrorIncompatible, wantTerminal: false}, | |||
| 383 | + | {code: BrokerErrorTimeout, wantTerminal: false}, | |||
| 384 | + | {code: BrokerErrorCancelled, wantTerminal: false}, | |||
| 385 | + | {code: BrokerErrorOutcomeUnknown, wantTerminal: false}, | |||
| 386 | + | {code: BrokerErrorAttachmentLost, wantTerminal: false}, | |||
| 387 | + | {code: BrokerErrorStaleEpoch, wantTerminal: false}, | |||
| 388 | + | {code: BrokerErrorConflictingPolicy, wantTerminal: false}, | |||
| 389 | + | {code: BrokerErrorExplicitExit, wantTerminal: true}, | |||
| 390 | + | {code: BrokerErrorFatalTerminal, wantTerminal: true}, | |||
| 391 | + | } | |||
| 392 | + | for _, tt := range tests { | |||
| 393 | + | t.Run(tt.code.String(), func(t *testing.T) { | |||
| 394 | + | failure := BrokerError{Code: tt.code, Text: "visible typed error"} | |||
| 395 | + | if err := failure.Validate(); err != nil { | |||
| 396 | + | t.Fatalf("Validate() = %v", err) | |||
| 397 | + | } | |||
| 398 | + | if got := failure.Terminal(); got != tt.wantTerminal { | |||
| 399 | + | t.Fatalf("Terminal() = %t, want %t", got, tt.wantTerminal) | |||
| 400 | + | } | |||
| 401 | + | if failure.Error() == "" { | |||
| 402 | + | t.Fatal("Error() must render a message") | |||
| 403 | + | } | |||
| 404 | + | }) | |||
| 405 | + | } | |||
| 406 | + | if err := (BrokerError{Code: BrokerErrorCode(0)}).Validate(); err == nil { | |||
| 407 | + | t.Fatal("zero broker error code accepted") | |||
| 408 | + | } | |||
| 409 | + | oversize := BrokerError{Code: BrokerErrorTimeout, Text: strings.Repeat("e", BrokerMaxErrorBytes+1)} | |||
| 410 | + | if err := oversize.Validate(); err == nil { | |||
| 411 | + | t.Fatal("oversize broker error text accepted") | |||
| 412 | + | } | |||
| 413 | + | unsafe := BrokerError{Code: BrokerErrorTimeout, Text: "timeout\u202erorrE"} | |||
| 414 | + | if err := unsafe.Validate(); err == nil { | |||
| 415 | + | t.Fatal("bidirectional control in broker error accepted") | |||
| 416 | + | } | |||
| 417 | + | } | |||
| 418 | + | ||||
| 419 | + | func TestBrokerIdentityScopesStayDistinct(t *testing.T) { | |||
| 420 | + | // Every identity level validates independently and zero values never | |||
| 421 | + | // identify anything: connection, stream, operation, daemon incarnation, | |||
| 422 | + | // registration, session lifecycle, and broker epoch are distinct types. | |||
| 423 | + | if err := (BrokerConnectionID{}).Validate(); err == nil { | |||
| 424 | + | t.Fatal("zero connection ID accepted") | |||
| 425 | + | } | |||
| 426 | + | if err := BrokerStreamID(0).Validate(); err == nil { | |||
| 427 | + | t.Fatal("zero stream ID accepted") | |||
| 428 | + | } | |||
| 429 | + | if err := (BrokerOperationID{}).Validate(); err == nil { | |||
| 430 | + | t.Fatal("zero operation ID accepted") | |||
| 431 | + | } | |||
| 432 | + | if err := (BrokerDaemonIncarnation{}).Validate(); err == nil { | |||
| 433 | + | t.Fatal("zero daemon incarnation accepted") | |||
| 434 | + | } | |||
| 435 | + | if err := (domain.RemoteRegistration{}).Validate(); err == nil { | |||
| 436 | + | t.Fatal("zero host registration accepted") | |||
| 437 | + | } | |||
| 438 | + | if err := (protocol.ExactSessionTarget{}).Validate(); err == nil { | |||
| 439 | + | t.Fatal("zero session lifecycle target accepted") | |||
| 440 | + | } | |||
| 441 | + | if err := BrokerDaemonIdentity("").Validate(); err == nil { | |||
| 442 | + | t.Fatal("empty daemon identity accepted") | |||
| 443 | + | } | |||
| 444 | + | if err := (BrokerStreamLost{}).Validate(); err == nil { | |||
| 445 | + | t.Fatal("zero stream loss accepted") | |||
| 446 | + | } | |||
| 447 | + | loss := BrokerStreamLost{ | |||
| 448 | + | Connection: BrokerConnectionID{1}, | |||
| 449 | + | Stream: 2, | |||
| 450 | + | Epoch: 3, | |||
| 451 | + | Cause: domain.RemoteFailureTimeout, | |||
| 452 | + | } | |||
| 453 | + | if err := loss.Validate(); err != nil { | |||
| 454 | + | t.Fatalf("Validate() = %v", err) | |||
| 455 | + | } | |||
| 456 | + | } | |||
| 457 | + | ||||
| 458 | + | func withBrokerPolicy(base BrokerPolicy, mutate func(*BrokerPolicy)) BrokerPolicy { | |||
| 459 | + | mutate(&base) | |||
| 460 | + | return base | |||
| 461 | + | } | |||
| 462 | + | ||||
| 463 | + | func TestBrokerInterfacesArePorts(t *testing.T) { | |||
| 464 | + | // Broker, client, and daemon cross-layer contracts are ports: the | |||
| 465 | + | // seams must stay interfaces so Mockery can generate mocks and | |||
| 466 | + | // adapters can implement them without importing use cases. | |||
| 467 | + | for name, iface := range map[string]reflect.Type{ | |||
| 468 | + | "BrokerService": reflect.TypeOf((*BrokerService)(nil)).Elem(), | |||
| 469 | + | "BrokerSnapshotStore": reflect.TypeOf((*BrokerSnapshotStore)(nil)).Elem(), | |||
| 470 | + | "BrokerPhysicalConnection": reflect.TypeOf((*BrokerPhysicalConnection)(nil)).Elem(), | |||
| 471 | + | "BrokerEndpointConnector": reflect.TypeOf((*BrokerEndpointConnector)(nil)).Elem(), | |||
| 472 | + | "BrokerListener": reflect.TypeOf((*BrokerListener)(nil)).Elem(), | |||
| 473 | + | "BrokerSubscription": reflect.TypeOf((*BrokerSubscription)(nil)).Elem(), | |||
| 474 | + | } { | |||
| 475 | + | if iface.Kind() != reflect.Interface { | |||
| 476 | + | t.Fatalf("%s must stay an interface, got %v", name, iface.Kind()) | |||
| 477 | + | } | |||
| 478 | + | } | |||
| 479 | + | } | |||
A internal/ports/mocks/broker_mock.go +1297 -0
| @@ -0,0 +1,1297 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package portsmocks | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "context" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/vev/internal/ports" | |
| 11 | + | mock "github.com/stretchr/testify/mock" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // NewMockBrokerSubscription creates a new instance of MockBrokerSubscription. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 15 | + | // The first argument is typically a *testing.T value. | |
| 16 | + | func NewMockBrokerSubscription(t interface { | |
| 17 | + | mock.TestingT | |
| 18 | + | Cleanup(func()) | |
| 19 | + | }) *MockBrokerSubscription { | |
| 20 | + | mock := &MockBrokerSubscription{} | |
| 21 | + | mock.Mock.Test(t) | |
| 22 | + | ||
| 23 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 24 | + | ||
| 25 | + | return mock | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | // MockBrokerSubscription is an autogenerated mock type for the BrokerSubscription type | |
| 29 | + | type MockBrokerSubscription struct { | |
| 30 | + | mock.Mock | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | type MockBrokerSubscription_Expecter struct { | |
| 34 | + | mock *mock.Mock | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | func (_m *MockBrokerSubscription) EXPECT() *MockBrokerSubscription_Expecter { | |
| 38 | + | return &MockBrokerSubscription_Expecter{mock: &_m.Mock} | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | // Changed provides a mock function for the type MockBrokerSubscription | |
| 42 | + | func (_mock *MockBrokerSubscription) Changed() <-chan struct{} { | |
| 43 | + | ret := _mock.Called() | |
| 44 | + | ||
| 45 | + | if len(ret) == 0 { | |
| 46 | + | panic("no return value specified for Changed") | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | var r0 <-chan struct{} | |
| 50 | + | if returnFunc, ok := ret.Get(0).(func() <-chan struct{}); ok { | |
| 51 | + | r0 = returnFunc() | |
| 52 | + | } else { | |
| 53 | + | if ret.Get(0) != nil { | |
| 54 | + | r0 = ret.Get(0).(<-chan struct{}) | |
| 55 | + | } | |
| 56 | + | } | |
| 57 | + | return r0 | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | // MockBrokerSubscription_Changed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Changed' | |
| 61 | + | type MockBrokerSubscription_Changed_Call struct { | |
| 62 | + | *mock.Call | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | // Changed is a helper method to define mock.On call | |
| 66 | + | func (_e *MockBrokerSubscription_Expecter) Changed() *MockBrokerSubscription_Changed_Call { | |
| 67 | + | return &MockBrokerSubscription_Changed_Call{Call: _e.mock.On("Changed")} | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | func (_c *MockBrokerSubscription_Changed_Call) Run(run func()) *MockBrokerSubscription_Changed_Call { | |
| 71 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 72 | + | run() | |
| 73 | + | }) | |
| 74 | + | return _c | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | func (_c *MockBrokerSubscription_Changed_Call) Return(valCh <-chan struct{}) *MockBrokerSubscription_Changed_Call { | |
| 78 | + | _c.Call.Return(valCh) | |
| 79 | + | return _c | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | func (_c *MockBrokerSubscription_Changed_Call) RunAndReturn(run func() <-chan struct{}) *MockBrokerSubscription_Changed_Call { | |
| 83 | + | _c.Call.Return(run) | |
| 84 | + | return _c | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | // Close provides a mock function for the type MockBrokerSubscription | |
| 88 | + | func (_mock *MockBrokerSubscription) Close() { | |
| 89 | + | _mock.Called() | |
| 90 | + | return | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | // MockBrokerSubscription_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |
| 94 | + | type MockBrokerSubscription_Close_Call struct { | |
| 95 | + | *mock.Call | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | // Close is a helper method to define mock.On call | |
| 99 | + | func (_e *MockBrokerSubscription_Expecter) Close() *MockBrokerSubscription_Close_Call { | |
| 100 | + | return &MockBrokerSubscription_Close_Call{Call: _e.mock.On("Close")} | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | func (_c *MockBrokerSubscription_Close_Call) Run(run func()) *MockBrokerSubscription_Close_Call { | |
| 104 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 105 | + | run() | |
| 106 | + | }) | |
| 107 | + | return _c | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | func (_c *MockBrokerSubscription_Close_Call) Return() *MockBrokerSubscription_Close_Call { | |
| 111 | + | _c.Call.Return() | |
| 112 | + | return _c | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | func (_c *MockBrokerSubscription_Close_Call) RunAndReturn(run func()) *MockBrokerSubscription_Close_Call { | |
| 116 | + | _c.Run(run) | |
| 117 | + | return _c | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | // NewMockBrokerService creates a new instance of MockBrokerService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 121 | + | // The first argument is typically a *testing.T value. | |
| 122 | + | func NewMockBrokerService(t interface { | |
| 123 | + | mock.TestingT | |
| 124 | + | Cleanup(func()) | |
| 125 | + | }) *MockBrokerService { | |
| 126 | + | mock := &MockBrokerService{} | |
| 127 | + | mock.Mock.Test(t) | |
| 128 | + | ||
| 129 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 130 | + | ||
| 131 | + | return mock | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | // MockBrokerService is an autogenerated mock type for the BrokerService type | |
| 135 | + | type MockBrokerService struct { | |
| 136 | + | mock.Mock | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | type MockBrokerService_Expecter struct { | |
| 140 | + | mock *mock.Mock | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | func (_m *MockBrokerService) EXPECT() *MockBrokerService_Expecter { | |
| 144 | + | return &MockBrokerService_Expecter{mock: &_m.Mock} | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | // AddHost provides a mock function for the type MockBrokerService | |
| 148 | + | func (_mock *MockBrokerService) AddHost(ctx context.Context, target string) error { | |
| 149 | + | ret := _mock.Called(ctx, target) | |
| 150 | + | ||
| 151 | + | if len(ret) == 0 { | |
| 152 | + | panic("no return value specified for AddHost") | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | var r0 error | |
| 156 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { | |
| 157 | + | r0 = returnFunc(ctx, target) | |
| 158 | + | } else { | |
| 159 | + | r0 = ret.Error(0) | |
| 160 | + | } | |
| 161 | + | return r0 | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | // MockBrokerService_AddHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddHost' | |
| 165 | + | type MockBrokerService_AddHost_Call struct { | |
| 166 | + | *mock.Call | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | // AddHost is a helper method to define mock.On call | |
| 170 | + | // - ctx context.Context | |
| 171 | + | // - target string | |
| 172 | + | func (_e *MockBrokerService_Expecter) AddHost(ctx any, target any) *MockBrokerService_AddHost_Call { | |
| 173 | + | return &MockBrokerService_AddHost_Call{Call: _e.mock.On("AddHost", ctx, target)} | |
| 174 | + | } | |
| 175 | + | ||
| 176 | + | func (_c *MockBrokerService_AddHost_Call) Run(run func(ctx context.Context, target string)) *MockBrokerService_AddHost_Call { | |
| 177 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 178 | + | var arg0 context.Context | |
| 179 | + | if args[0] != nil { | |
| 180 | + | arg0 = args[0].(context.Context) | |
| 181 | + | } | |
| 182 | + | var arg1 string | |
| 183 | + | if args[1] != nil { | |
| 184 | + | arg1 = args[1].(string) | |
| 185 | + | } | |
| 186 | + | run( | |
| 187 | + | arg0, | |
| 188 | + | arg1, | |
| 189 | + | ) | |
| 190 | + | }) | |
| 191 | + | return _c | |
| 192 | + | } | |
| 193 | + | ||
| 194 | + | func (_c *MockBrokerService_AddHost_Call) Return(err error) *MockBrokerService_AddHost_Call { | |
| 195 | + | _c.Call.Return(err) | |
| 196 | + | return _c | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | func (_c *MockBrokerService_AddHost_Call) RunAndReturn(run func(ctx context.Context, target string) error) *MockBrokerService_AddHost_Call { | |
| 200 | + | _c.Call.Return(run) | |
| 201 | + | return _c | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | // Close provides a mock function for the type MockBrokerService | |
| 205 | + | func (_mock *MockBrokerService) Close() error { | |
| 206 | + | ret := _mock.Called() | |
| 207 | + | ||
| 208 | + | if len(ret) == 0 { | |
| 209 | + | panic("no return value specified for Close") | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | var r0 error | |
| 213 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 214 | + | r0 = returnFunc() | |
| 215 | + | } else { | |
| 216 | + | r0 = ret.Error(0) | |
| 217 | + | } | |
| 218 | + | return r0 | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | // MockBrokerService_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |
| 222 | + | type MockBrokerService_Close_Call struct { | |
| 223 | + | *mock.Call | |
| 224 | + | } | |
| 225 | + | ||
| 226 | + | // Close is a helper method to define mock.On call | |
| 227 | + | func (_e *MockBrokerService_Expecter) Close() *MockBrokerService_Close_Call { | |
| 228 | + | return &MockBrokerService_Close_Call{Call: _e.mock.On("Close")} | |
| 229 | + | } | |
| 230 | + | ||
| 231 | + | func (_c *MockBrokerService_Close_Call) Run(run func()) *MockBrokerService_Close_Call { | |
| 232 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 233 | + | run() | |
| 234 | + | }) | |
| 235 | + | return _c | |
| 236 | + | } | |
| 237 | + | ||
| 238 | + | func (_c *MockBrokerService_Close_Call) Return(err error) *MockBrokerService_Close_Call { | |
| 239 | + | _c.Call.Return(err) | |
| 240 | + | return _c | |
| 241 | + | } | |
| 242 | + | ||
| 243 | + | func (_c *MockBrokerService_Close_Call) RunAndReturn(run func() error) *MockBrokerService_Close_Call { | |
| 244 | + | _c.Call.Return(run) | |
| 245 | + | return _c | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | // CloseStream provides a mock function for the type MockBrokerService | |
| 249 | + | func (_mock *MockBrokerService) CloseStream(connection ports.BrokerConnectionID, stream ports.BrokerStreamID) error { | |
| 250 | + | ret := _mock.Called(connection, stream) | |
| 251 | + | ||
| 252 | + | if len(ret) == 0 { | |
| 253 | + | panic("no return value specified for CloseStream") | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | var r0 error | |
| 257 | + | if returnFunc, ok := ret.Get(0).(func(ports.BrokerConnectionID, ports.BrokerStreamID) error); ok { | |
| 258 | + | r0 = returnFunc(connection, stream) | |
| 259 | + | } else { | |
| 260 | + | r0 = ret.Error(0) | |
| 261 | + | } | |
| 262 | + | return r0 | |
| 263 | + | } | |
| 264 | + | ||
| 265 | + | // MockBrokerService_CloseStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseStream' | |
| 266 | + | type MockBrokerService_CloseStream_Call struct { | |
| 267 | + | *mock.Call | |
| 268 | + | } | |
| 269 | + | ||
| 270 | + | // CloseStream is a helper method to define mock.On call | |
| 271 | + | // - connection ports.BrokerConnectionID | |
| 272 | + | // - stream ports.BrokerStreamID | |
| 273 | + | func (_e *MockBrokerService_Expecter) CloseStream(connection any, stream any) *MockBrokerService_CloseStream_Call { | |
| 274 | + | return &MockBrokerService_CloseStream_Call{Call: _e.mock.On("CloseStream", connection, stream)} | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | func (_c *MockBrokerService_CloseStream_Call) Run(run func(connection ports.BrokerConnectionID, stream ports.BrokerStreamID)) *MockBrokerService_CloseStream_Call { | |
| 278 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 279 | + | var arg0 ports.BrokerConnectionID | |
| 280 | + | if args[0] != nil { | |
| 281 | + | arg0 = args[0].(ports.BrokerConnectionID) | |
| 282 | + | } | |
| 283 | + | var arg1 ports.BrokerStreamID | |
| 284 | + | if args[1] != nil { | |
| 285 | + | arg1 = args[1].(ports.BrokerStreamID) | |
| 286 | + | } | |
| 287 | + | run( | |
| 288 | + | arg0, | |
| 289 | + | arg1, | |
| 290 | + | ) | |
| 291 | + | }) | |
| 292 | + | return _c | |
| 293 | + | } | |
| 294 | + | ||
| 295 | + | func (_c *MockBrokerService_CloseStream_Call) Return(err error) *MockBrokerService_CloseStream_Call { | |
| 296 | + | _c.Call.Return(err) | |
| 297 | + | return _c | |
| 298 | + | } | |
| 299 | + | ||
| 300 | + | func (_c *MockBrokerService_CloseStream_Call) RunAndReturn(run func(connection ports.BrokerConnectionID, stream ports.BrokerStreamID) error) *MockBrokerService_CloseStream_Call { | |
| 301 | + | _c.Call.Return(run) | |
| 302 | + | return _c | |
| 303 | + | } | |
| 304 | + | ||
| 305 | + | // ConnectionID provides a mock function for the type MockBrokerService | |
| 306 | + | func (_mock *MockBrokerService) ConnectionID() ports.BrokerConnectionID { | |
| 307 | + | ret := _mock.Called() | |
| 308 | + | ||
| 309 | + | if len(ret) == 0 { | |
| 310 | + | panic("no return value specified for ConnectionID") | |
| 311 | + | } | |
| 312 | + | ||
| 313 | + | var r0 ports.BrokerConnectionID | |
| 314 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerConnectionID); ok { | |
| 315 | + | r0 = returnFunc() | |
| 316 | + | } else { | |
| 317 | + | if ret.Get(0) != nil { | |
| 318 | + | r0 = ret.Get(0).(ports.BrokerConnectionID) | |
| 319 | + | } | |
| 320 | + | } | |
| 321 | + | return r0 | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | // MockBrokerService_ConnectionID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ConnectionID' | |
| 325 | + | type MockBrokerService_ConnectionID_Call struct { | |
| 326 | + | *mock.Call | |
| 327 | + | } | |
| 328 | + | ||
| 329 | + | // ConnectionID is a helper method to define mock.On call | |
| 330 | + | func (_e *MockBrokerService_Expecter) ConnectionID() *MockBrokerService_ConnectionID_Call { | |
| 331 | + | return &MockBrokerService_ConnectionID_Call{Call: _e.mock.On("ConnectionID")} | |
| 332 | + | } | |
| 333 | + | ||
| 334 | + | func (_c *MockBrokerService_ConnectionID_Call) Run(run func()) *MockBrokerService_ConnectionID_Call { | |
| 335 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 336 | + | run() | |
| 337 | + | }) | |
| 338 | + | return _c | |
| 339 | + | } | |
| 340 | + | ||
| 341 | + | func (_c *MockBrokerService_ConnectionID_Call) Return(brokerConnectionID ports.BrokerConnectionID) *MockBrokerService_ConnectionID_Call { | |
| 342 | + | _c.Call.Return(brokerConnectionID) | |
| 343 | + | return _c | |
| 344 | + | } | |
| 345 | + | ||
| 346 | + | func (_c *MockBrokerService_ConnectionID_Call) RunAndReturn(run func() ports.BrokerConnectionID) *MockBrokerService_ConnectionID_Call { | |
| 347 | + | _c.Call.Return(run) | |
| 348 | + | return _c | |
| 349 | + | } | |
| 350 | + | ||
| 351 | + | // OpenStream provides a mock function for the type MockBrokerService | |
| 352 | + | func (_mock *MockBrokerService) OpenStream(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.ClientConnection, error) { | |
| 353 | + | ret := _mock.Called(ctx, request) | |
| 354 | + | ||
| 355 | + | if len(ret) == 0 { | |
| 356 | + | panic("no return value specified for OpenStream") | |
| 357 | + | } | |
| 358 | + | ||
| 359 | + | var r0 ports.ClientConnection | |
| 360 | + | var r1 error | |
| 361 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, ports.BrokerOpenStreamRequest) (ports.ClientConnection, error)); ok { | |
| 362 | + | return returnFunc(ctx, request) | |
| 363 | + | } | |
| 364 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, ports.BrokerOpenStreamRequest) ports.ClientConnection); ok { | |
| 365 | + | r0 = returnFunc(ctx, request) | |
| 366 | + | } else { | |
| 367 | + | if ret.Get(0) != nil { | |
| 368 | + | r0 = ret.Get(0).(ports.ClientConnection) | |
| 369 | + | } | |
| 370 | + | } | |
| 371 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, ports.BrokerOpenStreamRequest) error); ok { | |
| 372 | + | r1 = returnFunc(ctx, request) | |
| 373 | + | } else { | |
| 374 | + | r1 = ret.Error(1) | |
| 375 | + | } | |
| 376 | + | return r0, r1 | |
| 377 | + | } | |
| 378 | + | ||
| 379 | + | // MockBrokerService_OpenStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenStream' | |
| 380 | + | type MockBrokerService_OpenStream_Call struct { | |
| 381 | + | *mock.Call | |
| 382 | + | } | |
| 383 | + | ||
| 384 | + | // OpenStream is a helper method to define mock.On call | |
| 385 | + | // - ctx context.Context | |
| 386 | + | // - request ports.BrokerOpenStreamRequest | |
| 387 | + | func (_e *MockBrokerService_Expecter) OpenStream(ctx any, request any) *MockBrokerService_OpenStream_Call { | |
| 388 | + | return &MockBrokerService_OpenStream_Call{Call: _e.mock.On("OpenStream", ctx, request)} | |
| 389 | + | } | |
| 390 | + | ||
| 391 | + | func (_c *MockBrokerService_OpenStream_Call) Run(run func(ctx context.Context, request ports.BrokerOpenStreamRequest)) *MockBrokerService_OpenStream_Call { | |
| 392 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 393 | + | var arg0 context.Context | |
| 394 | + | if args[0] != nil { | |
| 395 | + | arg0 = args[0].(context.Context) | |
| 396 | + | } | |
| 397 | + | var arg1 ports.BrokerOpenStreamRequest | |
| 398 | + | if args[1] != nil { | |
| 399 | + | arg1 = args[1].(ports.BrokerOpenStreamRequest) | |
| 400 | + | } | |
| 401 | + | run( | |
| 402 | + | arg0, | |
| 403 | + | arg1, | |
| 404 | + | ) | |
| 405 | + | }) | |
| 406 | + | return _c | |
| 407 | + | } | |
| 408 | + | ||
| 409 | + | func (_c *MockBrokerService_OpenStream_Call) Return(clientConnection ports.ClientConnection, err error) *MockBrokerService_OpenStream_Call { | |
| 410 | + | _c.Call.Return(clientConnection, err) | |
| 411 | + | return _c | |
| 412 | + | } | |
| 413 | + | ||
| 414 | + | func (_c *MockBrokerService_OpenStream_Call) RunAndReturn(run func(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.ClientConnection, error)) *MockBrokerService_OpenStream_Call { | |
| 415 | + | _c.Call.Return(run) | |
| 416 | + | return _c | |
| 417 | + | } | |
| 418 | + | ||
| 419 | + | // RemoveHost provides a mock function for the type MockBrokerService | |
| 420 | + | func (_mock *MockBrokerService) RemoveHost(ctx context.Context, target string) (bool, error) { | |
| 421 | + | ret := _mock.Called(ctx, target) | |
| 422 | + | ||
| 423 | + | if len(ret) == 0 { | |
| 424 | + | panic("no return value specified for RemoveHost") | |
| 425 | + | } | |
| 426 | + | ||
| 427 | + | var r0 bool | |
| 428 | + | var r1 error | |
| 429 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { | |
| 430 | + | return returnFunc(ctx, target) | |
| 431 | + | } | |
| 432 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) bool); ok { | |
| 433 | + | r0 = returnFunc(ctx, target) | |
| 434 | + | } else { | |
| 435 | + | r0 = ret.Get(0).(bool) | |
| 436 | + | } | |
| 437 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { | |
| 438 | + | r1 = returnFunc(ctx, target) | |
| 439 | + | } else { | |
| 440 | + | r1 = ret.Error(1) | |
| 441 | + | } | |
| 442 | + | return r0, r1 | |
| 443 | + | } | |
| 444 | + | ||
| 445 | + | // MockBrokerService_RemoveHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveHost' | |
| 446 | + | type MockBrokerService_RemoveHost_Call struct { | |
| 447 | + | *mock.Call | |
| 448 | + | } | |
| 449 | + | ||
| 450 | + | // RemoveHost is a helper method to define mock.On call | |
| 451 | + | // - ctx context.Context | |
| 452 | + | // - target string | |
| 453 | + | func (_e *MockBrokerService_Expecter) RemoveHost(ctx any, target any) *MockBrokerService_RemoveHost_Call { | |
| 454 | + | return &MockBrokerService_RemoveHost_Call{Call: _e.mock.On("RemoveHost", ctx, target)} | |
| 455 | + | } | |
| 456 | + | ||
| 457 | + | func (_c *MockBrokerService_RemoveHost_Call) Run(run func(ctx context.Context, target string)) *MockBrokerService_RemoveHost_Call { | |
| 458 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 459 | + | var arg0 context.Context | |
| 460 | + | if args[0] != nil { | |
| 461 | + | arg0 = args[0].(context.Context) | |
| 462 | + | } | |
| 463 | + | var arg1 string | |
| 464 | + | if args[1] != nil { | |
| 465 | + | arg1 = args[1].(string) | |
| 466 | + | } | |
| 467 | + | run( | |
| 468 | + | arg0, | |
| 469 | + | arg1, | |
| 470 | + | ) | |
| 471 | + | }) | |
| 472 | + | return _c | |
| 473 | + | } | |
| 474 | + | ||
| 475 | + | func (_c *MockBrokerService_RemoveHost_Call) Return(b bool, err error) *MockBrokerService_RemoveHost_Call { | |
| 476 | + | _c.Call.Return(b, err) | |
| 477 | + | return _c | |
| 478 | + | } | |
| 479 | + | ||
| 480 | + | func (_c *MockBrokerService_RemoveHost_Call) RunAndReturn(run func(ctx context.Context, target string) (bool, error)) *MockBrokerService_RemoveHost_Call { | |
| 481 | + | _c.Call.Return(run) | |
| 482 | + | return _c | |
| 483 | + | } | |
| 484 | + | ||
| 485 | + | // RequestReconcile provides a mock function for the type MockBrokerService | |
| 486 | + | func (_mock *MockBrokerService) RequestReconcile(endpoint string) { | |
| 487 | + | _mock.Called(endpoint) | |
| 488 | + | return | |
| 489 | + | } | |
| 490 | + | ||
| 491 | + | // MockBrokerService_RequestReconcile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RequestReconcile' | |
| 492 | + | type MockBrokerService_RequestReconcile_Call struct { | |
| 493 | + | *mock.Call | |
| 494 | + | } | |
| 495 | + | ||
| 496 | + | // RequestReconcile is a helper method to define mock.On call | |
| 497 | + | // - endpoint string | |
| 498 | + | func (_e *MockBrokerService_Expecter) RequestReconcile(endpoint any) *MockBrokerService_RequestReconcile_Call { | |
| 499 | + | return &MockBrokerService_RequestReconcile_Call{Call: _e.mock.On("RequestReconcile", endpoint)} | |
| 500 | + | } | |
| 501 | + | ||
| 502 | + | func (_c *MockBrokerService_RequestReconcile_Call) Run(run func(endpoint string)) *MockBrokerService_RequestReconcile_Call { | |
| 503 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 504 | + | var arg0 string | |
| 505 | + | if args[0] != nil { | |
| 506 | + | arg0 = args[0].(string) | |
| 507 | + | } | |
| 508 | + | run( | |
| 509 | + | arg0, | |
| 510 | + | ) | |
| 511 | + | }) | |
| 512 | + | return _c | |
| 513 | + | } | |
| 514 | + | ||
| 515 | + | func (_c *MockBrokerService_RequestReconcile_Call) Return() *MockBrokerService_RequestReconcile_Call { | |
| 516 | + | _c.Call.Return() | |
| 517 | + | return _c | |
| 518 | + | } | |
| 519 | + | ||
| 520 | + | func (_c *MockBrokerService_RequestReconcile_Call) RunAndReturn(run func(endpoint string)) *MockBrokerService_RequestReconcile_Call { | |
| 521 | + | _c.Run(run) | |
| 522 | + | return _c | |
| 523 | + | } | |
| 524 | + | ||
| 525 | + | // Snapshot provides a mock function for the type MockBrokerService | |
| 526 | + | func (_mock *MockBrokerService) Snapshot() ports.BrokerSnapshot { | |
| 527 | + | ret := _mock.Called() | |
| 528 | + | ||
| 529 | + | if len(ret) == 0 { | |
| 530 | + | panic("no return value specified for Snapshot") | |
| 531 | + | } | |
| 532 | + | ||
| 533 | + | var r0 ports.BrokerSnapshot | |
| 534 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerSnapshot); ok { | |
| 535 | + | r0 = returnFunc() | |
| 536 | + | } else { | |
| 537 | + | r0 = ret.Get(0).(ports.BrokerSnapshot) | |
| 538 | + | } | |
| 539 | + | return r0 | |
| 540 | + | } | |
| 541 | + | ||
| 542 | + | // MockBrokerService_Snapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Snapshot' | |
| 543 | + | type MockBrokerService_Snapshot_Call struct { | |
| 544 | + | *mock.Call | |
| 545 | + | } | |
| 546 | + | ||
| 547 | + | // Snapshot is a helper method to define mock.On call | |
| 548 | + | func (_e *MockBrokerService_Expecter) Snapshot() *MockBrokerService_Snapshot_Call { | |
| 549 | + | return &MockBrokerService_Snapshot_Call{Call: _e.mock.On("Snapshot")} | |
| 550 | + | } | |
| 551 | + | ||
| 552 | + | func (_c *MockBrokerService_Snapshot_Call) Run(run func()) *MockBrokerService_Snapshot_Call { | |
| 553 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 554 | + | run() | |
| 555 | + | }) | |
| 556 | + | return _c | |
| 557 | + | } | |
| 558 | + | ||
| 559 | + | func (_c *MockBrokerService_Snapshot_Call) Return(brokerSnapshot ports.BrokerSnapshot) *MockBrokerService_Snapshot_Call { | |
| 560 | + | _c.Call.Return(brokerSnapshot) | |
| 561 | + | return _c | |
| 562 | + | } | |
| 563 | + | ||
| 564 | + | func (_c *MockBrokerService_Snapshot_Call) RunAndReturn(run func() ports.BrokerSnapshot) *MockBrokerService_Snapshot_Call { | |
| 565 | + | _c.Call.Return(run) | |
| 566 | + | return _c | |
| 567 | + | } | |
| 568 | + | ||
| 569 | + | // Subscribe provides a mock function for the type MockBrokerService | |
| 570 | + | func (_mock *MockBrokerService) Subscribe() (ports.BrokerSubscription, error) { | |
| 571 | + | ret := _mock.Called() | |
| 572 | + | ||
| 573 | + | if len(ret) == 0 { | |
| 574 | + | panic("no return value specified for Subscribe") | |
| 575 | + | } | |
| 576 | + | ||
| 577 | + | var r0 ports.BrokerSubscription | |
| 578 | + | var r1 error | |
| 579 | + | if returnFunc, ok := ret.Get(0).(func() (ports.BrokerSubscription, error)); ok { | |
| 580 | + | return returnFunc() | |
| 581 | + | } | |
| 582 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerSubscription); ok { | |
| 583 | + | r0 = returnFunc() | |
| 584 | + | } else { | |
| 585 | + | if ret.Get(0) != nil { | |
| 586 | + | r0 = ret.Get(0).(ports.BrokerSubscription) | |
| 587 | + | } | |
| 588 | + | } | |
| 589 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |
| 590 | + | r1 = returnFunc() | |
| 591 | + | } else { | |
| 592 | + | r1 = ret.Error(1) | |
| 593 | + | } | |
| 594 | + | return r0, r1 | |
| 595 | + | } | |
| 596 | + | ||
| 597 | + | // MockBrokerService_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' | |
| 598 | + | type MockBrokerService_Subscribe_Call struct { | |
| 599 | + | *mock.Call | |
| 600 | + | } | |
| 601 | + | ||
| 602 | + | // Subscribe is a helper method to define mock.On call | |
| 603 | + | func (_e *MockBrokerService_Expecter) Subscribe() *MockBrokerService_Subscribe_Call { | |
| 604 | + | return &MockBrokerService_Subscribe_Call{Call: _e.mock.On("Subscribe")} | |
| 605 | + | } | |
| 606 | + | ||
| 607 | + | func (_c *MockBrokerService_Subscribe_Call) Run(run func()) *MockBrokerService_Subscribe_Call { | |
| 608 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 609 | + | run() | |
| 610 | + | }) | |
| 611 | + | return _c | |
| 612 | + | } | |
| 613 | + | ||
| 614 | + | func (_c *MockBrokerService_Subscribe_Call) Return(brokerSubscription ports.BrokerSubscription, err error) *MockBrokerService_Subscribe_Call { | |
| 615 | + | _c.Call.Return(brokerSubscription, err) | |
| 616 | + | return _c | |
| 617 | + | } | |
| 618 | + | ||
| 619 | + | func (_c *MockBrokerService_Subscribe_Call) RunAndReturn(run func() (ports.BrokerSubscription, error)) *MockBrokerService_Subscribe_Call { | |
| 620 | + | _c.Call.Return(run) | |
| 621 | + | return _c | |
| 622 | + | } | |
| 623 | + | ||
| 624 | + | // NewMockBrokerSnapshotStore creates a new instance of MockBrokerSnapshotStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 625 | + | // The first argument is typically a *testing.T value. | |
| 626 | + | func NewMockBrokerSnapshotStore(t interface { | |
| 627 | + | mock.TestingT | |
| 628 | + | Cleanup(func()) | |
| 629 | + | }) *MockBrokerSnapshotStore { | |
| 630 | + | mock := &MockBrokerSnapshotStore{} | |
| 631 | + | mock.Mock.Test(t) | |
| 632 | + | ||
| 633 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 634 | + | ||
| 635 | + | return mock | |
| 636 | + | } | |
| 637 | + | ||
| 638 | + | // MockBrokerSnapshotStore is an autogenerated mock type for the BrokerSnapshotStore type | |
| 639 | + | type MockBrokerSnapshotStore struct { | |
| 640 | + | mock.Mock | |
| 641 | + | } | |
| 642 | + | ||
| 643 | + | type MockBrokerSnapshotStore_Expecter struct { | |
| 644 | + | mock *mock.Mock | |
| 645 | + | } | |
| 646 | + | ||
| 647 | + | func (_m *MockBrokerSnapshotStore) EXPECT() *MockBrokerSnapshotStore_Expecter { | |
| 648 | + | return &MockBrokerSnapshotStore_Expecter{mock: &_m.Mock} | |
| 649 | + | } | |
| 650 | + | ||
| 651 | + | // Load provides a mock function for the type MockBrokerSnapshotStore | |
| 652 | + | func (_mock *MockBrokerSnapshotStore) Load() (ports.BrokerSnapshot, error) { | |
| 653 | + | ret := _mock.Called() | |
| 654 | + | ||
| 655 | + | if len(ret) == 0 { | |
| 656 | + | panic("no return value specified for Load") | |
| 657 | + | } | |
| 658 | + | ||
| 659 | + | var r0 ports.BrokerSnapshot | |
| 660 | + | var r1 error | |
| 661 | + | if returnFunc, ok := ret.Get(0).(func() (ports.BrokerSnapshot, error)); ok { | |
| 662 | + | return returnFunc() | |
| 663 | + | } | |
| 664 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerSnapshot); ok { | |
| 665 | + | r0 = returnFunc() | |
| 666 | + | } else { | |
| 667 | + | r0 = ret.Get(0).(ports.BrokerSnapshot) | |
| 668 | + | } | |
| 669 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |
| 670 | + | r1 = returnFunc() | |
| 671 | + | } else { | |
| 672 | + | r1 = ret.Error(1) | |
| 673 | + | } | |
| 674 | + | return r0, r1 | |
| 675 | + | } | |
| 676 | + | ||
| 677 | + | // MockBrokerSnapshotStore_Load_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Load' | |
| 678 | + | type MockBrokerSnapshotStore_Load_Call struct { | |
| 679 | + | *mock.Call | |
| 680 | + | } | |
| 681 | + | ||
| 682 | + | // Load is a helper method to define mock.On call | |
| 683 | + | func (_e *MockBrokerSnapshotStore_Expecter) Load() *MockBrokerSnapshotStore_Load_Call { | |
| 684 | + | return &MockBrokerSnapshotStore_Load_Call{Call: _e.mock.On("Load")} | |
| 685 | + | } | |
| 686 | + | ||
| 687 | + | func (_c *MockBrokerSnapshotStore_Load_Call) Run(run func()) *MockBrokerSnapshotStore_Load_Call { | |
| 688 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 689 | + | run() | |
| 690 | + | }) | |
| 691 | + | return _c | |
| 692 | + | } | |
| 693 | + | ||
| 694 | + | func (_c *MockBrokerSnapshotStore_Load_Call) Return(brokerSnapshot ports.BrokerSnapshot, err error) *MockBrokerSnapshotStore_Load_Call { | |
| 695 | + | _c.Call.Return(brokerSnapshot, err) | |
| 696 | + | return _c | |
| 697 | + | } | |
| 698 | + | ||
| 699 | + | func (_c *MockBrokerSnapshotStore_Load_Call) RunAndReturn(run func() (ports.BrokerSnapshot, error)) *MockBrokerSnapshotStore_Load_Call { | |
| 700 | + | _c.Call.Return(run) | |
| 701 | + | return _c | |
| 702 | + | } | |
| 703 | + | ||
| 704 | + | // Store provides a mock function for the type MockBrokerSnapshotStore | |
| 705 | + | func (_mock *MockBrokerSnapshotStore) Store(brokerSnapshot ports.BrokerSnapshot) error { | |
| 706 | + | ret := _mock.Called(brokerSnapshot) | |
| 707 | + | ||
| 708 | + | if len(ret) == 0 { | |
| 709 | + | panic("no return value specified for Store") | |
| 710 | + | } | |
| 711 | + | ||
| 712 | + | var r0 error | |
| 713 | + | if returnFunc, ok := ret.Get(0).(func(ports.BrokerSnapshot) error); ok { | |
| 714 | + | r0 = returnFunc(brokerSnapshot) | |
| 715 | + | } else { | |
| 716 | + | r0 = ret.Error(0) | |
| 717 | + | } | |
| 718 | + | return r0 | |
| 719 | + | } | |
| 720 | + | ||
| 721 | + | // MockBrokerSnapshotStore_Store_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Store' | |
| 722 | + | type MockBrokerSnapshotStore_Store_Call struct { | |
| 723 | + | *mock.Call | |
| 724 | + | } | |
| 725 | + | ||
| 726 | + | // Store is a helper method to define mock.On call | |
| 727 | + | // - brokerSnapshot ports.BrokerSnapshot | |
| 728 | + | func (_e *MockBrokerSnapshotStore_Expecter) Store(brokerSnapshot any) *MockBrokerSnapshotStore_Store_Call { | |
| 729 | + | return &MockBrokerSnapshotStore_Store_Call{Call: _e.mock.On("Store", brokerSnapshot)} | |
| 730 | + | } | |
| 731 | + | ||
| 732 | + | func (_c *MockBrokerSnapshotStore_Store_Call) Run(run func(brokerSnapshot ports.BrokerSnapshot)) *MockBrokerSnapshotStore_Store_Call { | |
| 733 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 734 | + | var arg0 ports.BrokerSnapshot | |
| 735 | + | if args[0] != nil { | |
| 736 | + | arg0 = args[0].(ports.BrokerSnapshot) | |
| 737 | + | } | |
| 738 | + | run( | |
| 739 | + | arg0, | |
| 740 | + | ) | |
| 741 | + | }) | |
| 742 | + | return _c | |
| 743 | + | } | |
| 744 | + | ||
| 745 | + | func (_c *MockBrokerSnapshotStore_Store_Call) Return(err error) *MockBrokerSnapshotStore_Store_Call { | |
| 746 | + | _c.Call.Return(err) | |
| 747 | + | return _c | |
| 748 | + | } | |
| 749 | + | ||
| 750 | + | func (_c *MockBrokerSnapshotStore_Store_Call) RunAndReturn(run func(brokerSnapshot ports.BrokerSnapshot) error) *MockBrokerSnapshotStore_Store_Call { | |
| 751 | + | _c.Call.Return(run) | |
| 752 | + | return _c | |
| 753 | + | } | |
| 754 | + | ||
| 755 | + | // NewMockBrokerPhysicalConnection creates a new instance of MockBrokerPhysicalConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 756 | + | // The first argument is typically a *testing.T value. | |
| 757 | + | func NewMockBrokerPhysicalConnection(t interface { | |
| 758 | + | mock.TestingT | |
| 759 | + | Cleanup(func()) | |
| 760 | + | }) *MockBrokerPhysicalConnection { | |
| 761 | + | mock := &MockBrokerPhysicalConnection{} | |
| 762 | + | mock.Mock.Test(t) | |
| 763 | + | ||
| 764 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 765 | + | ||
| 766 | + | return mock | |
| 767 | + | } | |
| 768 | + | ||
| 769 | + | // MockBrokerPhysicalConnection is an autogenerated mock type for the BrokerPhysicalConnection type | |
| 770 | + | type MockBrokerPhysicalConnection struct { | |
| 771 | + | mock.Mock | |
| 772 | + | } | |
| 773 | + | ||
| 774 | + | type MockBrokerPhysicalConnection_Expecter struct { | |
| 775 | + | mock *mock.Mock | |
| 776 | + | } | |
| 777 | + | ||
| 778 | + | func (_m *MockBrokerPhysicalConnection) EXPECT() *MockBrokerPhysicalConnection_Expecter { | |
| 779 | + | return &MockBrokerPhysicalConnection_Expecter{mock: &_m.Mock} | |
| 780 | + | } | |
| 781 | + | ||
| 782 | + | // Close provides a mock function for the type MockBrokerPhysicalConnection | |
| 783 | + | func (_mock *MockBrokerPhysicalConnection) Close() error { | |
| 784 | + | ret := _mock.Called() | |
| 785 | + | ||
| 786 | + | if len(ret) == 0 { | |
| 787 | + | panic("no return value specified for Close") | |
| 788 | + | } | |
| 789 | + | ||
| 790 | + | var r0 error | |
| 791 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 792 | + | r0 = returnFunc() | |
| 793 | + | } else { | |
| 794 | + | r0 = ret.Error(0) | |
| 795 | + | } | |
| 796 | + | return r0 | |
| 797 | + | } | |
| 798 | + | ||
| 799 | + | // MockBrokerPhysicalConnection_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |
| 800 | + | type MockBrokerPhysicalConnection_Close_Call struct { | |
| 801 | + | *mock.Call | |
| 802 | + | } | |
| 803 | + | ||
| 804 | + | // Close is a helper method to define mock.On call | |
| 805 | + | func (_e *MockBrokerPhysicalConnection_Expecter) Close() *MockBrokerPhysicalConnection_Close_Call { | |
| 806 | + | return &MockBrokerPhysicalConnection_Close_Call{Call: _e.mock.On("Close")} | |
| 807 | + | } | |
| 808 | + | ||
| 809 | + | func (_c *MockBrokerPhysicalConnection_Close_Call) Run(run func()) *MockBrokerPhysicalConnection_Close_Call { | |
| 810 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 811 | + | run() | |
| 812 | + | }) | |
| 813 | + | return _c | |
| 814 | + | } | |
| 815 | + | ||
| 816 | + | func (_c *MockBrokerPhysicalConnection_Close_Call) Return(err error) *MockBrokerPhysicalConnection_Close_Call { | |
| 817 | + | _c.Call.Return(err) | |
| 818 | + | return _c | |
| 819 | + | } | |
| 820 | + | ||
| 821 | + | func (_c *MockBrokerPhysicalConnection_Close_Call) RunAndReturn(run func() error) *MockBrokerPhysicalConnection_Close_Call { | |
| 822 | + | _c.Call.Return(run) | |
| 823 | + | return _c | |
| 824 | + | } | |
| 825 | + | ||
| 826 | + | // Identity provides a mock function for the type MockBrokerPhysicalConnection | |
| 827 | + | func (_mock *MockBrokerPhysicalConnection) Identity() ports.BrokerDaemonIdentity { | |
| 828 | + | ret := _mock.Called() | |
| 829 | + | ||
| 830 | + | if len(ret) == 0 { | |
| 831 | + | panic("no return value specified for Identity") | |
| 832 | + | } | |
| 833 | + | ||
| 834 | + | var r0 ports.BrokerDaemonIdentity | |
| 835 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerDaemonIdentity); ok { | |
| 836 | + | r0 = returnFunc() | |
| 837 | + | } else { | |
| 838 | + | r0 = ret.Get(0).(ports.BrokerDaemonIdentity) | |
| 839 | + | } | |
| 840 | + | return r0 | |
| 841 | + | } | |
| 842 | + | ||
| 843 | + | // MockBrokerPhysicalConnection_Identity_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Identity' | |
| 844 | + | type MockBrokerPhysicalConnection_Identity_Call struct { | |
| 845 | + | *mock.Call | |
| 846 | + | } | |
| 847 | + | ||
| 848 | + | // Identity is a helper method to define mock.On call | |
| 849 | + | func (_e *MockBrokerPhysicalConnection_Expecter) Identity() *MockBrokerPhysicalConnection_Identity_Call { | |
| 850 | + | return &MockBrokerPhysicalConnection_Identity_Call{Call: _e.mock.On("Identity")} | |
| 851 | + | } | |
| 852 | + | ||
| 853 | + | func (_c *MockBrokerPhysicalConnection_Identity_Call) Run(run func()) *MockBrokerPhysicalConnection_Identity_Call { | |
| 854 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 855 | + | run() | |
| 856 | + | }) | |
| 857 | + | return _c | |
| 858 | + | } | |
| 859 | + | ||
| 860 | + | func (_c *MockBrokerPhysicalConnection_Identity_Call) Return(brokerDaemonIdentity ports.BrokerDaemonIdentity) *MockBrokerPhysicalConnection_Identity_Call { | |
| 861 | + | _c.Call.Return(brokerDaemonIdentity) | |
| 862 | + | return _c | |
| 863 | + | } | |
| 864 | + | ||
| 865 | + | func (_c *MockBrokerPhysicalConnection_Identity_Call) RunAndReturn(run func() ports.BrokerDaemonIdentity) *MockBrokerPhysicalConnection_Identity_Call { | |
| 866 | + | _c.Call.Return(run) | |
| 867 | + | return _c | |
| 868 | + | } | |
| 869 | + | ||
| 870 | + | // Incarnation provides a mock function for the type MockBrokerPhysicalConnection | |
| 871 | + | func (_mock *MockBrokerPhysicalConnection) Incarnation() ports.BrokerDaemonIncarnation { | |
| 872 | + | ret := _mock.Called() | |
| 873 | + | ||
| 874 | + | if len(ret) == 0 { | |
| 875 | + | panic("no return value specified for Incarnation") | |
| 876 | + | } | |
| 877 | + | ||
| 878 | + | var r0 ports.BrokerDaemonIncarnation | |
| 879 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerDaemonIncarnation); ok { | |
| 880 | + | r0 = returnFunc() | |
| 881 | + | } else { | |
| 882 | + | if ret.Get(0) != nil { | |
| 883 | + | r0 = ret.Get(0).(ports.BrokerDaemonIncarnation) | |
| 884 | + | } | |
| 885 | + | } | |
| 886 | + | return r0 | |
| 887 | + | } | |
| 888 | + | ||
| 889 | + | // MockBrokerPhysicalConnection_Incarnation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Incarnation' | |
| 890 | + | type MockBrokerPhysicalConnection_Incarnation_Call struct { | |
| 891 | + | *mock.Call | |
| 892 | + | } | |
| 893 | + | ||
| 894 | + | // Incarnation is a helper method to define mock.On call | |
| 895 | + | func (_e *MockBrokerPhysicalConnection_Expecter) Incarnation() *MockBrokerPhysicalConnection_Incarnation_Call { | |
| 896 | + | return &MockBrokerPhysicalConnection_Incarnation_Call{Call: _e.mock.On("Incarnation")} | |
| 897 | + | } | |
| 898 | + | ||
| 899 | + | func (_c *MockBrokerPhysicalConnection_Incarnation_Call) Run(run func()) *MockBrokerPhysicalConnection_Incarnation_Call { | |
| 900 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 901 | + | run() | |
| 902 | + | }) | |
| 903 | + | return _c | |
| 904 | + | } | |
| 905 | + | ||
| 906 | + | func (_c *MockBrokerPhysicalConnection_Incarnation_Call) Return(brokerDaemonIncarnation ports.BrokerDaemonIncarnation) *MockBrokerPhysicalConnection_Incarnation_Call { | |
| 907 | + | _c.Call.Return(brokerDaemonIncarnation) | |
| 908 | + | return _c | |
| 909 | + | } | |
| 910 | + | ||
| 911 | + | func (_c *MockBrokerPhysicalConnection_Incarnation_Call) RunAndReturn(run func() ports.BrokerDaemonIncarnation) *MockBrokerPhysicalConnection_Incarnation_Call { | |
| 912 | + | _c.Call.Return(run) | |
| 913 | + | return _c | |
| 914 | + | } | |
| 915 | + | ||
| 916 | + | // OpenStream provides a mock function for the type MockBrokerPhysicalConnection | |
| 917 | + | func (_mock *MockBrokerPhysicalConnection) OpenStream(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.ClientConnection, error) { | |
| 918 | + | ret := _mock.Called(ctx, request) | |
| 919 | + | ||
| 920 | + | if len(ret) == 0 { | |
| 921 | + | panic("no return value specified for OpenStream") | |
| 922 | + | } | |
| 923 | + | ||
| 924 | + | var r0 ports.ClientConnection | |
| 925 | + | var r1 error | |
| 926 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, ports.BrokerOpenStreamRequest) (ports.ClientConnection, error)); ok { | |
| 927 | + | return returnFunc(ctx, request) | |
| 928 | + | } | |
| 929 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, ports.BrokerOpenStreamRequest) ports.ClientConnection); ok { | |
| 930 | + | r0 = returnFunc(ctx, request) | |
| 931 | + | } else { | |
| 932 | + | if ret.Get(0) != nil { | |
| 933 | + | r0 = ret.Get(0).(ports.ClientConnection) | |
| 934 | + | } | |
| 935 | + | } | |
| 936 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, ports.BrokerOpenStreamRequest) error); ok { | |
| 937 | + | r1 = returnFunc(ctx, request) | |
| 938 | + | } else { | |
| 939 | + | r1 = ret.Error(1) | |
| 940 | + | } | |
| 941 | + | return r0, r1 | |
| 942 | + | } | |
| 943 | + | ||
| 944 | + | // MockBrokerPhysicalConnection_OpenStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenStream' | |
| 945 | + | type MockBrokerPhysicalConnection_OpenStream_Call struct { | |
| 946 | + | *mock.Call | |
| 947 | + | } | |
| 948 | + | ||
| 949 | + | // OpenStream is a helper method to define mock.On call | |
| 950 | + | // - ctx context.Context | |
| 951 | + | // - request ports.BrokerOpenStreamRequest | |
| 952 | + | func (_e *MockBrokerPhysicalConnection_Expecter) OpenStream(ctx any, request any) *MockBrokerPhysicalConnection_OpenStream_Call { | |
| 953 | + | return &MockBrokerPhysicalConnection_OpenStream_Call{Call: _e.mock.On("OpenStream", ctx, request)} | |
| 954 | + | } | |
| 955 | + | ||
| 956 | + | func (_c *MockBrokerPhysicalConnection_OpenStream_Call) Run(run func(ctx context.Context, request ports.BrokerOpenStreamRequest)) *MockBrokerPhysicalConnection_OpenStream_Call { | |
| 957 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 958 | + | var arg0 context.Context | |
| 959 | + | if args[0] != nil { | |
| 960 | + | arg0 = args[0].(context.Context) | |
| 961 | + | } | |
| 962 | + | var arg1 ports.BrokerOpenStreamRequest | |
| 963 | + | if args[1] != nil { | |
| 964 | + | arg1 = args[1].(ports.BrokerOpenStreamRequest) | |
| 965 | + | } | |
| 966 | + | run( | |
| 967 | + | arg0, | |
| 968 | + | arg1, | |
| 969 | + | ) | |
| 970 | + | }) | |
| 971 | + | return _c | |
| 972 | + | } | |
| 973 | + | ||
| 974 | + | func (_c *MockBrokerPhysicalConnection_OpenStream_Call) Return(clientConnection ports.ClientConnection, err error) *MockBrokerPhysicalConnection_OpenStream_Call { | |
| 975 | + | _c.Call.Return(clientConnection, err) | |
| 976 | + | return _c | |
| 977 | + | } | |
| 978 | + | ||
| 979 | + | func (_c *MockBrokerPhysicalConnection_OpenStream_Call) RunAndReturn(run func(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.ClientConnection, error)) *MockBrokerPhysicalConnection_OpenStream_Call { | |
| 980 | + | _c.Call.Return(run) | |
| 981 | + | return _c | |
| 982 | + | } | |
| 983 | + | ||
| 984 | + | // Policy provides a mock function for the type MockBrokerPhysicalConnection | |
| 985 | + | func (_mock *MockBrokerPhysicalConnection) Policy() ports.BrokerPolicy { | |
| 986 | + | ret := _mock.Called() | |
| 987 | + | ||
| 988 | + | if len(ret) == 0 { | |
| 989 | + | panic("no return value specified for Policy") | |
| 990 | + | } | |
| 991 | + | ||
| 992 | + | var r0 ports.BrokerPolicy | |
| 993 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerPolicy); ok { | |
| 994 | + | r0 = returnFunc() | |
| 995 | + | } else { | |
| 996 | + | r0 = ret.Get(0).(ports.BrokerPolicy) | |
| 997 | + | } | |
| 998 | + | return r0 | |
| 999 | + | } | |
| 1000 | + | ||
| 1001 | + | // MockBrokerPhysicalConnection_Policy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Policy' | |
| 1002 | + | type MockBrokerPhysicalConnection_Policy_Call struct { | |
| 1003 | + | *mock.Call | |
| 1004 | + | } | |
| 1005 | + | ||
| 1006 | + | // Policy is a helper method to define mock.On call | |
| 1007 | + | func (_e *MockBrokerPhysicalConnection_Expecter) Policy() *MockBrokerPhysicalConnection_Policy_Call { | |
| 1008 | + | return &MockBrokerPhysicalConnection_Policy_Call{Call: _e.mock.On("Policy")} | |
| 1009 | + | } | |
| 1010 | + | ||
| 1011 | + | func (_c *MockBrokerPhysicalConnection_Policy_Call) Run(run func()) *MockBrokerPhysicalConnection_Policy_Call { | |
| 1012 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 1013 | + | run() | |
| 1014 | + | }) | |
| 1015 | + | return _c | |
| 1016 | + | } | |
| 1017 | + | ||
| 1018 | + | func (_c *MockBrokerPhysicalConnection_Policy_Call) Return(brokerPolicy ports.BrokerPolicy) *MockBrokerPhysicalConnection_Policy_Call { | |
| 1019 | + | _c.Call.Return(brokerPolicy) | |
| 1020 | + | return _c | |
| 1021 | + | } | |
| 1022 | + | ||
| 1023 | + | func (_c *MockBrokerPhysicalConnection_Policy_Call) RunAndReturn(run func() ports.BrokerPolicy) *MockBrokerPhysicalConnection_Policy_Call { | |
| 1024 | + | _c.Call.Return(run) | |
| 1025 | + | return _c | |
| 1026 | + | } | |
| 1027 | + | ||
| 1028 | + | // NewMockBrokerEndpointConnector creates a new instance of MockBrokerEndpointConnector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 1029 | + | // The first argument is typically a *testing.T value. | |
| 1030 | + | func NewMockBrokerEndpointConnector(t interface { | |
| 1031 | + | mock.TestingT | |
| 1032 | + | Cleanup(func()) | |
| 1033 | + | }) *MockBrokerEndpointConnector { | |
| 1034 | + | mock := &MockBrokerEndpointConnector{} | |
| 1035 | + | mock.Mock.Test(t) | |
| 1036 | + | ||
| 1037 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 1038 | + | ||
| 1039 | + | return mock | |
| 1040 | + | } | |
| 1041 | + | ||
| 1042 | + | // MockBrokerEndpointConnector is an autogenerated mock type for the BrokerEndpointConnector type | |
| 1043 | + | type MockBrokerEndpointConnector struct { | |
| 1044 | + | mock.Mock | |
| 1045 | + | } | |
| 1046 | + | ||
| 1047 | + | type MockBrokerEndpointConnector_Expecter struct { | |
| 1048 | + | mock *mock.Mock | |
| 1049 | + | } | |
| 1050 | + | ||
| 1051 | + | func (_m *MockBrokerEndpointConnector) EXPECT() *MockBrokerEndpointConnector_Expecter { | |
| 1052 | + | return &MockBrokerEndpointConnector_Expecter{mock: &_m.Mock} | |
| 1053 | + | } | |
| 1054 | + | ||
| 1055 | + | // Connect provides a mock function for the type MockBrokerEndpointConnector | |
| 1056 | + | func (_mock *MockBrokerEndpointConnector) Connect(ctx context.Context, endpoint string, policy ports.BrokerPolicy) (ports.BrokerPhysicalConnection, error) { | |
| 1057 | + | ret := _mock.Called(ctx, endpoint, policy) | |
| 1058 | + | ||
| 1059 | + | if len(ret) == 0 { | |
| 1060 | + | panic("no return value specified for Connect") | |
| 1061 | + | } | |
| 1062 | + | ||
| 1063 | + | var r0 ports.BrokerPhysicalConnection | |
| 1064 | + | var r1 error | |
| 1065 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.BrokerPolicy) (ports.BrokerPhysicalConnection, error)); ok { | |
| 1066 | + | return returnFunc(ctx, endpoint, policy) | |
| 1067 | + | } | |
| 1068 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.BrokerPolicy) ports.BrokerPhysicalConnection); ok { | |
| 1069 | + | r0 = returnFunc(ctx, endpoint, policy) | |
| 1070 | + | } else { | |
| 1071 | + | if ret.Get(0) != nil { | |
| 1072 | + | r0 = ret.Get(0).(ports.BrokerPhysicalConnection) | |
| 1073 | + | } | |
| 1074 | + | } | |
| 1075 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string, ports.BrokerPolicy) error); ok { | |
| 1076 | + | r1 = returnFunc(ctx, endpoint, policy) | |
| 1077 | + | } else { | |
| 1078 | + | r1 = ret.Error(1) | |
| 1079 | + | } | |
| 1080 | + | return r0, r1 | |
| 1081 | + | } | |
| 1082 | + | ||
| 1083 | + | // MockBrokerEndpointConnector_Connect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Connect' | |
| 1084 | + | type MockBrokerEndpointConnector_Connect_Call struct { | |
| 1085 | + | *mock.Call | |
| 1086 | + | } | |
| 1087 | + | ||
| 1088 | + | // Connect is a helper method to define mock.On call | |
| 1089 | + | // - ctx context.Context | |
| 1090 | + | // - endpoint string | |
| 1091 | + | // - policy ports.BrokerPolicy | |
| 1092 | + | func (_e *MockBrokerEndpointConnector_Expecter) Connect(ctx any, endpoint any, policy any) *MockBrokerEndpointConnector_Connect_Call { | |
| 1093 | + | return &MockBrokerEndpointConnector_Connect_Call{Call: _e.mock.On("Connect", ctx, endpoint, policy)} | |
| 1094 | + | } | |
| 1095 | + | ||
| 1096 | + | func (_c *MockBrokerEndpointConnector_Connect_Call) Run(run func(ctx context.Context, endpoint string, policy ports.BrokerPolicy)) *MockBrokerEndpointConnector_Connect_Call { | |
| 1097 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 1098 | + | var arg0 context.Context | |
| 1099 | + | if args[0] != nil { | |
| 1100 | + | arg0 = args[0].(context.Context) | |
| 1101 | + | } | |
| 1102 | + | var arg1 string | |
| 1103 | + | if args[1] != nil { | |
| 1104 | + | arg1 = args[1].(string) | |
| 1105 | + | } | |
| 1106 | + | var arg2 ports.BrokerPolicy | |
| 1107 | + | if args[2] != nil { | |
| 1108 | + | arg2 = args[2].(ports.BrokerPolicy) | |
| 1109 | + | } | |
| 1110 | + | run( | |
| 1111 | + | arg0, | |
| 1112 | + | arg1, | |
| 1113 | + | arg2, | |
| 1114 | + | ) | |
| 1115 | + | }) | |
| 1116 | + | return _c | |
| 1117 | + | } | |
| 1118 | + | ||
| 1119 | + | func (_c *MockBrokerEndpointConnector_Connect_Call) Return(brokerPhysicalConnection ports.BrokerPhysicalConnection, err error) *MockBrokerEndpointConnector_Connect_Call { | |
| 1120 | + | _c.Call.Return(brokerPhysicalConnection, err) | |
| 1121 | + | return _c | |
| 1122 | + | } | |
| 1123 | + | ||
| 1124 | + | func (_c *MockBrokerEndpointConnector_Connect_Call) RunAndReturn(run func(ctx context.Context, endpoint string, policy ports.BrokerPolicy) (ports.BrokerPhysicalConnection, error)) *MockBrokerEndpointConnector_Connect_Call { | |
| 1125 | + | _c.Call.Return(run) | |
| 1126 | + | return _c | |
| 1127 | + | } | |
| 1128 | + | ||
| 1129 | + | // NewMockBrokerListener creates a new instance of MockBrokerListener. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 1130 | + | // The first argument is typically a *testing.T value. | |
| 1131 | + | func NewMockBrokerListener(t interface { | |
| 1132 | + | mock.TestingT | |
| 1133 | + | Cleanup(func()) | |
| 1134 | + | }) *MockBrokerListener { | |
| 1135 | + | mock := &MockBrokerListener{} | |
| 1136 | + | mock.Mock.Test(t) | |
| 1137 | + | ||
| 1138 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 1139 | + | ||
| 1140 | + | return mock | |
| 1141 | + | } | |
| 1142 | + | ||
| 1143 | + | // MockBrokerListener is an autogenerated mock type for the BrokerListener type | |
| 1144 | + | type MockBrokerListener struct { | |
| 1145 | + | mock.Mock | |
| 1146 | + | } | |
| 1147 | + | ||
| 1148 | + | type MockBrokerListener_Expecter struct { | |
| 1149 | + | mock *mock.Mock | |
| 1150 | + | } | |
| 1151 | + | ||
| 1152 | + | func (_m *MockBrokerListener) EXPECT() *MockBrokerListener_Expecter { | |
| 1153 | + | return &MockBrokerListener_Expecter{mock: &_m.Mock} | |
| 1154 | + | } | |
| 1155 | + | ||
| 1156 | + | // Accept provides a mock function for the type MockBrokerListener | |
| 1157 | + | func (_mock *MockBrokerListener) Accept() (ports.BrokerService, error) { | |
| 1158 | + | ret := _mock.Called() | |
| 1159 | + | ||
| 1160 | + | if len(ret) == 0 { | |
| 1161 | + | panic("no return value specified for Accept") | |
| 1162 | + | } | |
| 1163 | + | ||
| 1164 | + | var r0 ports.BrokerService | |
| 1165 | + | var r1 error | |
| 1166 | + | if returnFunc, ok := ret.Get(0).(func() (ports.BrokerService, error)); ok { | |
| 1167 | + | return returnFunc() | |
| 1168 | + | } | |
| 1169 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerService); ok { | |
| 1170 | + | r0 = returnFunc() | |
| 1171 | + | } else { | |
| 1172 | + | if ret.Get(0) != nil { | |
| 1173 | + | r0 = ret.Get(0).(ports.BrokerService) | |
| 1174 | + | } | |
| 1175 | + | } | |
| 1176 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |
| 1177 | + | r1 = returnFunc() | |
| 1178 | + | } else { | |
| 1179 | + | r1 = ret.Error(1) | |
| 1180 | + | } | |
| 1181 | + | return r0, r1 | |
| 1182 | + | } | |
| 1183 | + | ||
| 1184 | + | // MockBrokerListener_Accept_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Accept' | |
| 1185 | + | type MockBrokerListener_Accept_Call struct { | |
| 1186 | + | *mock.Call | |
| 1187 | + | } | |
| 1188 | + | ||
| 1189 | + | // Accept is a helper method to define mock.On call | |
| 1190 | + | func (_e *MockBrokerListener_Expecter) Accept() *MockBrokerListener_Accept_Call { | |
| 1191 | + | return &MockBrokerListener_Accept_Call{Call: _e.mock.On("Accept")} | |
| 1192 | + | } | |
| 1193 | + | ||
| 1194 | + | func (_c *MockBrokerListener_Accept_Call) Run(run func()) *MockBrokerListener_Accept_Call { | |
| 1195 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 1196 | + | run() | |
| 1197 | + | }) | |
| 1198 | + | return _c | |
| 1199 | + | } | |
| 1200 | + | ||
| 1201 | + | func (_c *MockBrokerListener_Accept_Call) Return(brokerService ports.BrokerService, err error) *MockBrokerListener_Accept_Call { | |
| 1202 | + | _c.Call.Return(brokerService, err) | |
| 1203 | + | return _c | |
| 1204 | + | } | |
| 1205 | + | ||
| 1206 | + | func (_c *MockBrokerListener_Accept_Call) RunAndReturn(run func() (ports.BrokerService, error)) *MockBrokerListener_Accept_Call { | |
| 1207 | + | _c.Call.Return(run) | |
| 1208 | + | return _c | |
| 1209 | + | } | |
| 1210 | + | ||
| 1211 | + | // Addr provides a mock function for the type MockBrokerListener | |
| 1212 | + | func (_mock *MockBrokerListener) Addr() string { | |
| 1213 | + | ret := _mock.Called() | |
| 1214 | + | ||
| 1215 | + | if len(ret) == 0 { | |
| 1216 | + | panic("no return value specified for Addr") | |
| 1217 | + | } | |
| 1218 | + | ||
| 1219 | + | var r0 string | |
| 1220 | + | if returnFunc, ok := ret.Get(0).(func() string); ok { | |
| 1221 | + | r0 = returnFunc() | |
| 1222 | + | } else { | |
| 1223 | + | r0 = ret.Get(0).(string) | |
| 1224 | + | } | |
| 1225 | + | return r0 | |
| 1226 | + | } | |
| 1227 | + | ||
| 1228 | + | // MockBrokerListener_Addr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Addr' | |
| 1229 | + | type MockBrokerListener_Addr_Call struct { | |
| 1230 | + | *mock.Call | |
| 1231 | + | } | |
| 1232 | + | ||
| 1233 | + | // Addr is a helper method to define mock.On call | |
| 1234 | + | func (_e *MockBrokerListener_Expecter) Addr() *MockBrokerListener_Addr_Call { | |
| 1235 | + | return &MockBrokerListener_Addr_Call{Call: _e.mock.On("Addr")} | |
| 1236 | + | } | |
| 1237 | + | ||
| 1238 | + | func (_c *MockBrokerListener_Addr_Call) Run(run func()) *MockBrokerListener_Addr_Call { | |
| 1239 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 1240 | + | run() | |
| 1241 | + | }) | |
| 1242 | + | return _c | |
| 1243 | + | } | |
| 1244 | + | ||
| 1245 | + | func (_c *MockBrokerListener_Addr_Call) Return(s string) *MockBrokerListener_Addr_Call { | |
| 1246 | + | _c.Call.Return(s) | |
| 1247 | + | return _c | |
| 1248 | + | } | |
| 1249 | + | ||
| 1250 | + | func (_c *MockBrokerListener_Addr_Call) RunAndReturn(run func() string) *MockBrokerListener_Addr_Call { | |
| 1251 | + | _c.Call.Return(run) | |
| 1252 | + | return _c | |
| 1253 | + | } | |
| 1254 | + | ||
| 1255 | + | // Close provides a mock function for the type MockBrokerListener | |
| 1256 | + | func (_mock *MockBrokerListener) Close() error { | |
| 1257 | + | ret := _mock.Called() | |
| 1258 | + | ||
| 1259 | + | if len(ret) == 0 { | |
| 1260 | + | panic("no return value specified for Close") | |
| 1261 | + | } | |
| 1262 | + | ||
| 1263 | + | var r0 error | |
| 1264 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 1265 | + | r0 = returnFunc() | |
| 1266 | + | } else { | |
| 1267 | + | r0 = ret.Error(0) | |
| 1268 | + | } | |
| 1269 | + | return r0 | |
| 1270 | + | } | |
| 1271 | + | ||
| 1272 | + | // MockBrokerListener_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |
| 1273 | + | type MockBrokerListener_Close_Call struct { | |
| 1274 | + | *mock.Call | |
| 1275 | + | } | |
| 1276 | + | ||
| 1277 | + | // Close is a helper method to define mock.On call | |
| 1278 | + | func (_e *MockBrokerListener_Expecter) Close() *MockBrokerListener_Close_Call { | |
| 1279 | + | return &MockBrokerListener_Close_Call{Call: _e.mock.On("Close")} | |
| 1280 | + | } | |
| 1281 | + | ||
| 1282 | + | func (_c *MockBrokerListener_Close_Call) Run(run func()) *MockBrokerListener_Close_Call { | |
| 1283 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 1284 | + | run() | |
| 1285 | + | }) | |
| 1286 | + | return _c | |
| 1287 | + | } | |
| 1288 | + | ||
| 1289 | + | func (_c *MockBrokerListener_Close_Call) Return(err error) *MockBrokerListener_Close_Call { | |
| 1290 | + | _c.Call.Return(err) | |
| 1291 | + | return _c | |
| 1292 | + | } | |
| 1293 | + | ||
| 1294 | + | func (_c *MockBrokerListener_Close_Call) RunAndReturn(run func() error) *MockBrokerListener_Close_Call { | |
| 1295 | + | _c.Call.Return(run) | |
| 1296 | + | return _c | |
| 1297 | + | } | |
| @@ -0,0 +1,1297 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package portsmocks | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "context" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/vev/internal/ports" | |||
| 11 | + | mock "github.com/stretchr/testify/mock" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // NewMockBrokerSubscription creates a new instance of MockBrokerSubscription. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 15 | + | // The first argument is typically a *testing.T value. | |||
| 16 | + | func NewMockBrokerSubscription(t interface { | |||
| 17 | + | mock.TestingT | |||
| 18 | + | Cleanup(func()) | |||
| 19 | + | }) *MockBrokerSubscription { | |||
| 20 | + | mock := &MockBrokerSubscription{} | |||
| 21 | + | mock.Mock.Test(t) | |||
| 22 | + | ||||
| 23 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 24 | + | ||||
| 25 | + | return mock | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | // MockBrokerSubscription is an autogenerated mock type for the BrokerSubscription type | |||
| 29 | + | type MockBrokerSubscription struct { | |||
| 30 | + | mock.Mock | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | type MockBrokerSubscription_Expecter struct { | |||
| 34 | + | mock *mock.Mock | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | func (_m *MockBrokerSubscription) EXPECT() *MockBrokerSubscription_Expecter { | |||
| 38 | + | return &MockBrokerSubscription_Expecter{mock: &_m.Mock} | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | // Changed provides a mock function for the type MockBrokerSubscription | |||
| 42 | + | func (_mock *MockBrokerSubscription) Changed() <-chan struct{} { | |||
| 43 | + | ret := _mock.Called() | |||
| 44 | + | ||||
| 45 | + | if len(ret) == 0 { | |||
| 46 | + | panic("no return value specified for Changed") | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | var r0 <-chan struct{} | |||
| 50 | + | if returnFunc, ok := ret.Get(0).(func() <-chan struct{}); ok { | |||
| 51 | + | r0 = returnFunc() | |||
| 52 | + | } else { | |||
| 53 | + | if ret.Get(0) != nil { | |||
| 54 | + | r0 = ret.Get(0).(<-chan struct{}) | |||
| 55 | + | } | |||
| 56 | + | } | |||
| 57 | + | return r0 | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | // MockBrokerSubscription_Changed_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Changed' | |||
| 61 | + | type MockBrokerSubscription_Changed_Call struct { | |||
| 62 | + | *mock.Call | |||
| 63 | + | } | |||
| 64 | + | ||||
| 65 | + | // Changed is a helper method to define mock.On call | |||
| 66 | + | func (_e *MockBrokerSubscription_Expecter) Changed() *MockBrokerSubscription_Changed_Call { | |||
| 67 | + | return &MockBrokerSubscription_Changed_Call{Call: _e.mock.On("Changed")} | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | func (_c *MockBrokerSubscription_Changed_Call) Run(run func()) *MockBrokerSubscription_Changed_Call { | |||
| 71 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 72 | + | run() | |||
| 73 | + | }) | |||
| 74 | + | return _c | |||
| 75 | + | } | |||
| 76 | + | ||||
| 77 | + | func (_c *MockBrokerSubscription_Changed_Call) Return(valCh <-chan struct{}) *MockBrokerSubscription_Changed_Call { | |||
| 78 | + | _c.Call.Return(valCh) | |||
| 79 | + | return _c | |||
| 80 | + | } | |||
| 81 | + | ||||
| 82 | + | func (_c *MockBrokerSubscription_Changed_Call) RunAndReturn(run func() <-chan struct{}) *MockBrokerSubscription_Changed_Call { | |||
| 83 | + | _c.Call.Return(run) | |||
| 84 | + | return _c | |||
| 85 | + | } | |||
| 86 | + | ||||
| 87 | + | // Close provides a mock function for the type MockBrokerSubscription | |||
| 88 | + | func (_mock *MockBrokerSubscription) Close() { | |||
| 89 | + | _mock.Called() | |||
| 90 | + | return | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | // MockBrokerSubscription_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |||
| 94 | + | type MockBrokerSubscription_Close_Call struct { | |||
| 95 | + | *mock.Call | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | // Close is a helper method to define mock.On call | |||
| 99 | + | func (_e *MockBrokerSubscription_Expecter) Close() *MockBrokerSubscription_Close_Call { | |||
| 100 | + | return &MockBrokerSubscription_Close_Call{Call: _e.mock.On("Close")} | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | func (_c *MockBrokerSubscription_Close_Call) Run(run func()) *MockBrokerSubscription_Close_Call { | |||
| 104 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 105 | + | run() | |||
| 106 | + | }) | |||
| 107 | + | return _c | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | func (_c *MockBrokerSubscription_Close_Call) Return() *MockBrokerSubscription_Close_Call { | |||
| 111 | + | _c.Call.Return() | |||
| 112 | + | return _c | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | func (_c *MockBrokerSubscription_Close_Call) RunAndReturn(run func()) *MockBrokerSubscription_Close_Call { | |||
| 116 | + | _c.Run(run) | |||
| 117 | + | return _c | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | // NewMockBrokerService creates a new instance of MockBrokerService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 121 | + | // The first argument is typically a *testing.T value. | |||
| 122 | + | func NewMockBrokerService(t interface { | |||
| 123 | + | mock.TestingT | |||
| 124 | + | Cleanup(func()) | |||
| 125 | + | }) *MockBrokerService { | |||
| 126 | + | mock := &MockBrokerService{} | |||
| 127 | + | mock.Mock.Test(t) | |||
| 128 | + | ||||
| 129 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 130 | + | ||||
| 131 | + | return mock | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | // MockBrokerService is an autogenerated mock type for the BrokerService type | |||
| 135 | + | type MockBrokerService struct { | |||
| 136 | + | mock.Mock | |||
| 137 | + | } | |||
| 138 | + | ||||
| 139 | + | type MockBrokerService_Expecter struct { | |||
| 140 | + | mock *mock.Mock | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | func (_m *MockBrokerService) EXPECT() *MockBrokerService_Expecter { | |||
| 144 | + | return &MockBrokerService_Expecter{mock: &_m.Mock} | |||
| 145 | + | } | |||
| 146 | + | ||||
| 147 | + | // AddHost provides a mock function for the type MockBrokerService | |||
| 148 | + | func (_mock *MockBrokerService) AddHost(ctx context.Context, target string) error { | |||
| 149 | + | ret := _mock.Called(ctx, target) | |||
| 150 | + | ||||
| 151 | + | if len(ret) == 0 { | |||
| 152 | + | panic("no return value specified for AddHost") | |||
| 153 | + | } | |||
| 154 | + | ||||
| 155 | + | var r0 error | |||
| 156 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { | |||
| 157 | + | r0 = returnFunc(ctx, target) | |||
| 158 | + | } else { | |||
| 159 | + | r0 = ret.Error(0) | |||
| 160 | + | } | |||
| 161 | + | return r0 | |||
| 162 | + | } | |||
| 163 | + | ||||
| 164 | + | // MockBrokerService_AddHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddHost' | |||
| 165 | + | type MockBrokerService_AddHost_Call struct { | |||
| 166 | + | *mock.Call | |||
| 167 | + | } | |||
| 168 | + | ||||
| 169 | + | // AddHost is a helper method to define mock.On call | |||
| 170 | + | // - ctx context.Context | |||
| 171 | + | // - target string | |||
| 172 | + | func (_e *MockBrokerService_Expecter) AddHost(ctx any, target any) *MockBrokerService_AddHost_Call { | |||
| 173 | + | return &MockBrokerService_AddHost_Call{Call: _e.mock.On("AddHost", ctx, target)} | |||
| 174 | + | } | |||
| 175 | + | ||||
| 176 | + | func (_c *MockBrokerService_AddHost_Call) Run(run func(ctx context.Context, target string)) *MockBrokerService_AddHost_Call { | |||
| 177 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 178 | + | var arg0 context.Context | |||
| 179 | + | if args[0] != nil { | |||
| 180 | + | arg0 = args[0].(context.Context) | |||
| 181 | + | } | |||
| 182 | + | var arg1 string | |||
| 183 | + | if args[1] != nil { | |||
| 184 | + | arg1 = args[1].(string) | |||
| 185 | + | } | |||
| 186 | + | run( | |||
| 187 | + | arg0, | |||
| 188 | + | arg1, | |||
| 189 | + | ) | |||
| 190 | + | }) | |||
| 191 | + | return _c | |||
| 192 | + | } | |||
| 193 | + | ||||
| 194 | + | func (_c *MockBrokerService_AddHost_Call) Return(err error) *MockBrokerService_AddHost_Call { | |||
| 195 | + | _c.Call.Return(err) | |||
| 196 | + | return _c | |||
| 197 | + | } | |||
| 198 | + | ||||
| 199 | + | func (_c *MockBrokerService_AddHost_Call) RunAndReturn(run func(ctx context.Context, target string) error) *MockBrokerService_AddHost_Call { | |||
| 200 | + | _c.Call.Return(run) | |||
| 201 | + | return _c | |||
| 202 | + | } | |||
| 203 | + | ||||
| 204 | + | // Close provides a mock function for the type MockBrokerService | |||
| 205 | + | func (_mock *MockBrokerService) Close() error { | |||
| 206 | + | ret := _mock.Called() | |||
| 207 | + | ||||
| 208 | + | if len(ret) == 0 { | |||
| 209 | + | panic("no return value specified for Close") | |||
| 210 | + | } | |||
| 211 | + | ||||
| 212 | + | var r0 error | |||
| 213 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |||
| 214 | + | r0 = returnFunc() | |||
| 215 | + | } else { | |||
| 216 | + | r0 = ret.Error(0) | |||
| 217 | + | } | |||
| 218 | + | return r0 | |||
| 219 | + | } | |||
| 220 | + | ||||
| 221 | + | // MockBrokerService_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |||
| 222 | + | type MockBrokerService_Close_Call struct { | |||
| 223 | + | *mock.Call | |||
| 224 | + | } | |||
| 225 | + | ||||
| 226 | + | // Close is a helper method to define mock.On call | |||
| 227 | + | func (_e *MockBrokerService_Expecter) Close() *MockBrokerService_Close_Call { | |||
| 228 | + | return &MockBrokerService_Close_Call{Call: _e.mock.On("Close")} | |||
| 229 | + | } | |||
| 230 | + | ||||
| 231 | + | func (_c *MockBrokerService_Close_Call) Run(run func()) *MockBrokerService_Close_Call { | |||
| 232 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 233 | + | run() | |||
| 234 | + | }) | |||
| 235 | + | return _c | |||
| 236 | + | } | |||
| 237 | + | ||||
| 238 | + | func (_c *MockBrokerService_Close_Call) Return(err error) *MockBrokerService_Close_Call { | |||
| 239 | + | _c.Call.Return(err) | |||
| 240 | + | return _c | |||
| 241 | + | } | |||
| 242 | + | ||||
| 243 | + | func (_c *MockBrokerService_Close_Call) RunAndReturn(run func() error) *MockBrokerService_Close_Call { | |||
| 244 | + | _c.Call.Return(run) | |||
| 245 | + | return _c | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | // CloseStream provides a mock function for the type MockBrokerService | |||
| 249 | + | func (_mock *MockBrokerService) CloseStream(connection ports.BrokerConnectionID, stream ports.BrokerStreamID) error { | |||
| 250 | + | ret := _mock.Called(connection, stream) | |||
| 251 | + | ||||
| 252 | + | if len(ret) == 0 { | |||
| 253 | + | panic("no return value specified for CloseStream") | |||
| 254 | + | } | |||
| 255 | + | ||||
| 256 | + | var r0 error | |||
| 257 | + | if returnFunc, ok := ret.Get(0).(func(ports.BrokerConnectionID, ports.BrokerStreamID) error); ok { | |||
| 258 | + | r0 = returnFunc(connection, stream) | |||
| 259 | + | } else { | |||
| 260 | + | r0 = ret.Error(0) | |||
| 261 | + | } | |||
| 262 | + | return r0 | |||
| 263 | + | } | |||
| 264 | + | ||||
| 265 | + | // MockBrokerService_CloseStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseStream' | |||
| 266 | + | type MockBrokerService_CloseStream_Call struct { | |||
| 267 | + | *mock.Call | |||
| 268 | + | } | |||
| 269 | + | ||||
| 270 | + | // CloseStream is a helper method to define mock.On call | |||
| 271 | + | // - connection ports.BrokerConnectionID | |||
| 272 | + | // - stream ports.BrokerStreamID | |||
| 273 | + | func (_e *MockBrokerService_Expecter) CloseStream(connection any, stream any) *MockBrokerService_CloseStream_Call { | |||
| 274 | + | return &MockBrokerService_CloseStream_Call{Call: _e.mock.On("CloseStream", connection, stream)} | |||
| 275 | + | } | |||
| 276 | + | ||||
| 277 | + | func (_c *MockBrokerService_CloseStream_Call) Run(run func(connection ports.BrokerConnectionID, stream ports.BrokerStreamID)) *MockBrokerService_CloseStream_Call { | |||
| 278 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 279 | + | var arg0 ports.BrokerConnectionID | |||
| 280 | + | if args[0] != nil { | |||
| 281 | + | arg0 = args[0].(ports.BrokerConnectionID) | |||
| 282 | + | } | |||
| 283 | + | var arg1 ports.BrokerStreamID | |||
| 284 | + | if args[1] != nil { | |||
| 285 | + | arg1 = args[1].(ports.BrokerStreamID) | |||
| 286 | + | } | |||
| 287 | + | run( | |||
| 288 | + | arg0, | |||
| 289 | + | arg1, | |||
| 290 | + | ) | |||
| 291 | + | }) | |||
| 292 | + | return _c | |||
| 293 | + | } | |||
| 294 | + | ||||
| 295 | + | func (_c *MockBrokerService_CloseStream_Call) Return(err error) *MockBrokerService_CloseStream_Call { | |||
| 296 | + | _c.Call.Return(err) | |||
| 297 | + | return _c | |||
| 298 | + | } | |||
| 299 | + | ||||
| 300 | + | func (_c *MockBrokerService_CloseStream_Call) RunAndReturn(run func(connection ports.BrokerConnectionID, stream ports.BrokerStreamID) error) *MockBrokerService_CloseStream_Call { | |||
| 301 | + | _c.Call.Return(run) | |||
| 302 | + | return _c | |||
| 303 | + | } | |||
| 304 | + | ||||
| 305 | + | // ConnectionID provides a mock function for the type MockBrokerService | |||
| 306 | + | func (_mock *MockBrokerService) ConnectionID() ports.BrokerConnectionID { | |||
| 307 | + | ret := _mock.Called() | |||
| 308 | + | ||||
| 309 | + | if len(ret) == 0 { | |||
| 310 | + | panic("no return value specified for ConnectionID") | |||
| 311 | + | } | |||
| 312 | + | ||||
| 313 | + | var r0 ports.BrokerConnectionID | |||
| 314 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerConnectionID); ok { | |||
| 315 | + | r0 = returnFunc() | |||
| 316 | + | } else { | |||
| 317 | + | if ret.Get(0) != nil { | |||
| 318 | + | r0 = ret.Get(0).(ports.BrokerConnectionID) | |||
| 319 | + | } | |||
| 320 | + | } | |||
| 321 | + | return r0 | |||
| 322 | + | } | |||
| 323 | + | ||||
| 324 | + | // MockBrokerService_ConnectionID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ConnectionID' | |||
| 325 | + | type MockBrokerService_ConnectionID_Call struct { | |||
| 326 | + | *mock.Call | |||
| 327 | + | } | |||
| 328 | + | ||||
| 329 | + | // ConnectionID is a helper method to define mock.On call | |||
| 330 | + | func (_e *MockBrokerService_Expecter) ConnectionID() *MockBrokerService_ConnectionID_Call { | |||
| 331 | + | return &MockBrokerService_ConnectionID_Call{Call: _e.mock.On("ConnectionID")} | |||
| 332 | + | } | |||
| 333 | + | ||||
| 334 | + | func (_c *MockBrokerService_ConnectionID_Call) Run(run func()) *MockBrokerService_ConnectionID_Call { | |||
| 335 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 336 | + | run() | |||
| 337 | + | }) | |||
| 338 | + | return _c | |||
| 339 | + | } | |||
| 340 | + | ||||
| 341 | + | func (_c *MockBrokerService_ConnectionID_Call) Return(brokerConnectionID ports.BrokerConnectionID) *MockBrokerService_ConnectionID_Call { | |||
| 342 | + | _c.Call.Return(brokerConnectionID) | |||
| 343 | + | return _c | |||
| 344 | + | } | |||
| 345 | + | ||||
| 346 | + | func (_c *MockBrokerService_ConnectionID_Call) RunAndReturn(run func() ports.BrokerConnectionID) *MockBrokerService_ConnectionID_Call { | |||
| 347 | + | _c.Call.Return(run) | |||
| 348 | + | return _c | |||
| 349 | + | } | |||
| 350 | + | ||||
| 351 | + | // OpenStream provides a mock function for the type MockBrokerService | |||
| 352 | + | func (_mock *MockBrokerService) OpenStream(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.ClientConnection, error) { | |||
| 353 | + | ret := _mock.Called(ctx, request) | |||
| 354 | + | ||||
| 355 | + | if len(ret) == 0 { | |||
| 356 | + | panic("no return value specified for OpenStream") | |||
| 357 | + | } | |||
| 358 | + | ||||
| 359 | + | var r0 ports.ClientConnection | |||
| 360 | + | var r1 error | |||
| 361 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, ports.BrokerOpenStreamRequest) (ports.ClientConnection, error)); ok { | |||
| 362 | + | return returnFunc(ctx, request) | |||
| 363 | + | } | |||
| 364 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, ports.BrokerOpenStreamRequest) ports.ClientConnection); ok { | |||
| 365 | + | r0 = returnFunc(ctx, request) | |||
| 366 | + | } else { | |||
| 367 | + | if ret.Get(0) != nil { | |||
| 368 | + | r0 = ret.Get(0).(ports.ClientConnection) | |||
| 369 | + | } | |||
| 370 | + | } | |||
| 371 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, ports.BrokerOpenStreamRequest) error); ok { | |||
| 372 | + | r1 = returnFunc(ctx, request) | |||
| 373 | + | } else { | |||
| 374 | + | r1 = ret.Error(1) | |||
| 375 | + | } | |||
| 376 | + | return r0, r1 | |||
| 377 | + | } | |||
| 378 | + | ||||
| 379 | + | // MockBrokerService_OpenStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenStream' | |||
| 380 | + | type MockBrokerService_OpenStream_Call struct { | |||
| 381 | + | *mock.Call | |||
| 382 | + | } | |||
| 383 | + | ||||
| 384 | + | // OpenStream is a helper method to define mock.On call | |||
| 385 | + | // - ctx context.Context | |||
| 386 | + | // - request ports.BrokerOpenStreamRequest | |||
| 387 | + | func (_e *MockBrokerService_Expecter) OpenStream(ctx any, request any) *MockBrokerService_OpenStream_Call { | |||
| 388 | + | return &MockBrokerService_OpenStream_Call{Call: _e.mock.On("OpenStream", ctx, request)} | |||
| 389 | + | } | |||
| 390 | + | ||||
| 391 | + | func (_c *MockBrokerService_OpenStream_Call) Run(run func(ctx context.Context, request ports.BrokerOpenStreamRequest)) *MockBrokerService_OpenStream_Call { | |||
| 392 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 393 | + | var arg0 context.Context | |||
| 394 | + | if args[0] != nil { | |||
| 395 | + | arg0 = args[0].(context.Context) | |||
| 396 | + | } | |||
| 397 | + | var arg1 ports.BrokerOpenStreamRequest | |||
| 398 | + | if args[1] != nil { | |||
| 399 | + | arg1 = args[1].(ports.BrokerOpenStreamRequest) | |||
| 400 | + | } | |||
| 401 | + | run( | |||
| 402 | + | arg0, | |||
| 403 | + | arg1, | |||
| 404 | + | ) | |||
| 405 | + | }) | |||
| 406 | + | return _c | |||
| 407 | + | } | |||
| 408 | + | ||||
| 409 | + | func (_c *MockBrokerService_OpenStream_Call) Return(clientConnection ports.ClientConnection, err error) *MockBrokerService_OpenStream_Call { | |||
| 410 | + | _c.Call.Return(clientConnection, err) | |||
| 411 | + | return _c | |||
| 412 | + | } | |||
| 413 | + | ||||
| 414 | + | func (_c *MockBrokerService_OpenStream_Call) RunAndReturn(run func(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.ClientConnection, error)) *MockBrokerService_OpenStream_Call { | |||
| 415 | + | _c.Call.Return(run) | |||
| 416 | + | return _c | |||
| 417 | + | } | |||
| 418 | + | ||||
| 419 | + | // RemoveHost provides a mock function for the type MockBrokerService | |||
| 420 | + | func (_mock *MockBrokerService) RemoveHost(ctx context.Context, target string) (bool, error) { | |||
| 421 | + | ret := _mock.Called(ctx, target) | |||
| 422 | + | ||||
| 423 | + | if len(ret) == 0 { | |||
| 424 | + | panic("no return value specified for RemoveHost") | |||
| 425 | + | } | |||
| 426 | + | ||||
| 427 | + | var r0 bool | |||
| 428 | + | var r1 error | |||
| 429 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { | |||
| 430 | + | return returnFunc(ctx, target) | |||
| 431 | + | } | |||
| 432 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) bool); ok { | |||
| 433 | + | r0 = returnFunc(ctx, target) | |||
| 434 | + | } else { | |||
| 435 | + | r0 = ret.Get(0).(bool) | |||
| 436 | + | } | |||
| 437 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { | |||
| 438 | + | r1 = returnFunc(ctx, target) | |||
| 439 | + | } else { | |||
| 440 | + | r1 = ret.Error(1) | |||
| 441 | + | } | |||
| 442 | + | return r0, r1 | |||
| 443 | + | } | |||
| 444 | + | ||||
| 445 | + | // MockBrokerService_RemoveHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveHost' | |||
| 446 | + | type MockBrokerService_RemoveHost_Call struct { | |||
| 447 | + | *mock.Call | |||
| 448 | + | } | |||
| 449 | + | ||||
| 450 | + | // RemoveHost is a helper method to define mock.On call | |||
| 451 | + | // - ctx context.Context | |||
| 452 | + | // - target string | |||
| 453 | + | func (_e *MockBrokerService_Expecter) RemoveHost(ctx any, target any) *MockBrokerService_RemoveHost_Call { | |||
| 454 | + | return &MockBrokerService_RemoveHost_Call{Call: _e.mock.On("RemoveHost", ctx, target)} | |||
| 455 | + | } | |||
| 456 | + | ||||
| 457 | + | func (_c *MockBrokerService_RemoveHost_Call) Run(run func(ctx context.Context, target string)) *MockBrokerService_RemoveHost_Call { | |||
| 458 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 459 | + | var arg0 context.Context | |||
| 460 | + | if args[0] != nil { | |||
| 461 | + | arg0 = args[0].(context.Context) | |||
| 462 | + | } | |||
| 463 | + | var arg1 string | |||
| 464 | + | if args[1] != nil { | |||
| 465 | + | arg1 = args[1].(string) | |||
| 466 | + | } | |||
| 467 | + | run( | |||
| 468 | + | arg0, | |||
| 469 | + | arg1, | |||
| 470 | + | ) | |||
| 471 | + | }) | |||
| 472 | + | return _c | |||
| 473 | + | } | |||
| 474 | + | ||||
| 475 | + | func (_c *MockBrokerService_RemoveHost_Call) Return(b bool, err error) *MockBrokerService_RemoveHost_Call { | |||
| 476 | + | _c.Call.Return(b, err) | |||
| 477 | + | return _c | |||
| 478 | + | } | |||
| 479 | + | ||||
| 480 | + | func (_c *MockBrokerService_RemoveHost_Call) RunAndReturn(run func(ctx context.Context, target string) (bool, error)) *MockBrokerService_RemoveHost_Call { | |||
| 481 | + | _c.Call.Return(run) | |||
| 482 | + | return _c | |||
| 483 | + | } | |||
| 484 | + | ||||
| 485 | + | // RequestReconcile provides a mock function for the type MockBrokerService | |||
| 486 | + | func (_mock *MockBrokerService) RequestReconcile(endpoint string) { | |||
| 487 | + | _mock.Called(endpoint) | |||
| 488 | + | return | |||
| 489 | + | } | |||
| 490 | + | ||||
| 491 | + | // MockBrokerService_RequestReconcile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RequestReconcile' | |||
| 492 | + | type MockBrokerService_RequestReconcile_Call struct { | |||
| 493 | + | *mock.Call | |||
| 494 | + | } | |||
| 495 | + | ||||
| 496 | + | // RequestReconcile is a helper method to define mock.On call | |||
| 497 | + | // - endpoint string | |||
| 498 | + | func (_e *MockBrokerService_Expecter) RequestReconcile(endpoint any) *MockBrokerService_RequestReconcile_Call { | |||
| 499 | + | return &MockBrokerService_RequestReconcile_Call{Call: _e.mock.On("RequestReconcile", endpoint)} | |||
| 500 | + | } | |||
| 501 | + | ||||
| 502 | + | func (_c *MockBrokerService_RequestReconcile_Call) Run(run func(endpoint string)) *MockBrokerService_RequestReconcile_Call { | |||
| 503 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 504 | + | var arg0 string | |||
| 505 | + | if args[0] != nil { | |||
| 506 | + | arg0 = args[0].(string) | |||
| 507 | + | } | |||
| 508 | + | run( | |||
| 509 | + | arg0, | |||
| 510 | + | ) | |||
| 511 | + | }) | |||
| 512 | + | return _c | |||
| 513 | + | } | |||
| 514 | + | ||||
| 515 | + | func (_c *MockBrokerService_RequestReconcile_Call) Return() *MockBrokerService_RequestReconcile_Call { | |||
| 516 | + | _c.Call.Return() | |||
| 517 | + | return _c | |||
| 518 | + | } | |||
| 519 | + | ||||
| 520 | + | func (_c *MockBrokerService_RequestReconcile_Call) RunAndReturn(run func(endpoint string)) *MockBrokerService_RequestReconcile_Call { | |||
| 521 | + | _c.Run(run) | |||
| 522 | + | return _c | |||
| 523 | + | } | |||
| 524 | + | ||||
| 525 | + | // Snapshot provides a mock function for the type MockBrokerService | |||
| 526 | + | func (_mock *MockBrokerService) Snapshot() ports.BrokerSnapshot { | |||
| 527 | + | ret := _mock.Called() | |||
| 528 | + | ||||
| 529 | + | if len(ret) == 0 { | |||
| 530 | + | panic("no return value specified for Snapshot") | |||
| 531 | + | } | |||
| 532 | + | ||||
| 533 | + | var r0 ports.BrokerSnapshot | |||
| 534 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerSnapshot); ok { | |||
| 535 | + | r0 = returnFunc() | |||
| 536 | + | } else { | |||
| 537 | + | r0 = ret.Get(0).(ports.BrokerSnapshot) | |||
| 538 | + | } | |||
| 539 | + | return r0 | |||
| 540 | + | } | |||
| 541 | + | ||||
| 542 | + | // MockBrokerService_Snapshot_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Snapshot' | |||
| 543 | + | type MockBrokerService_Snapshot_Call struct { | |||
| 544 | + | *mock.Call | |||
| 545 | + | } | |||
| 546 | + | ||||
| 547 | + | // Snapshot is a helper method to define mock.On call | |||
| 548 | + | func (_e *MockBrokerService_Expecter) Snapshot() *MockBrokerService_Snapshot_Call { | |||
| 549 | + | return &MockBrokerService_Snapshot_Call{Call: _e.mock.On("Snapshot")} | |||
| 550 | + | } | |||
| 551 | + | ||||
| 552 | + | func (_c *MockBrokerService_Snapshot_Call) Run(run func()) *MockBrokerService_Snapshot_Call { | |||
| 553 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 554 | + | run() | |||
| 555 | + | }) | |||
| 556 | + | return _c | |||
| 557 | + | } | |||
| 558 | + | ||||
| 559 | + | func (_c *MockBrokerService_Snapshot_Call) Return(brokerSnapshot ports.BrokerSnapshot) *MockBrokerService_Snapshot_Call { | |||
| 560 | + | _c.Call.Return(brokerSnapshot) | |||
| 561 | + | return _c | |||
| 562 | + | } | |||
| 563 | + | ||||
| 564 | + | func (_c *MockBrokerService_Snapshot_Call) RunAndReturn(run func() ports.BrokerSnapshot) *MockBrokerService_Snapshot_Call { | |||
| 565 | + | _c.Call.Return(run) | |||
| 566 | + | return _c | |||
| 567 | + | } | |||
| 568 | + | ||||
| 569 | + | // Subscribe provides a mock function for the type MockBrokerService | |||
| 570 | + | func (_mock *MockBrokerService) Subscribe() (ports.BrokerSubscription, error) { | |||
| 571 | + | ret := _mock.Called() | |||
| 572 | + | ||||
| 573 | + | if len(ret) == 0 { | |||
| 574 | + | panic("no return value specified for Subscribe") | |||
| 575 | + | } | |||
| 576 | + | ||||
| 577 | + | var r0 ports.BrokerSubscription | |||
| 578 | + | var r1 error | |||
| 579 | + | if returnFunc, ok := ret.Get(0).(func() (ports.BrokerSubscription, error)); ok { | |||
| 580 | + | return returnFunc() | |||
| 581 | + | } | |||
| 582 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerSubscription); ok { | |||
| 583 | + | r0 = returnFunc() | |||
| 584 | + | } else { | |||
| 585 | + | if ret.Get(0) != nil { | |||
| 586 | + | r0 = ret.Get(0).(ports.BrokerSubscription) | |||
| 587 | + | } | |||
| 588 | + | } | |||
| 589 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |||
| 590 | + | r1 = returnFunc() | |||
| 591 | + | } else { | |||
| 592 | + | r1 = ret.Error(1) | |||
| 593 | + | } | |||
| 594 | + | return r0, r1 | |||
| 595 | + | } | |||
| 596 | + | ||||
| 597 | + | // MockBrokerService_Subscribe_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Subscribe' | |||
| 598 | + | type MockBrokerService_Subscribe_Call struct { | |||
| 599 | + | *mock.Call | |||
| 600 | + | } | |||
| 601 | + | ||||
| 602 | + | // Subscribe is a helper method to define mock.On call | |||
| 603 | + | func (_e *MockBrokerService_Expecter) Subscribe() *MockBrokerService_Subscribe_Call { | |||
| 604 | + | return &MockBrokerService_Subscribe_Call{Call: _e.mock.On("Subscribe")} | |||
| 605 | + | } | |||
| 606 | + | ||||
| 607 | + | func (_c *MockBrokerService_Subscribe_Call) Run(run func()) *MockBrokerService_Subscribe_Call { | |||
| 608 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 609 | + | run() | |||
| 610 | + | }) | |||
| 611 | + | return _c | |||
| 612 | + | } | |||
| 613 | + | ||||
| 614 | + | func (_c *MockBrokerService_Subscribe_Call) Return(brokerSubscription ports.BrokerSubscription, err error) *MockBrokerService_Subscribe_Call { | |||
| 615 | + | _c.Call.Return(brokerSubscription, err) | |||
| 616 | + | return _c | |||
| 617 | + | } | |||
| 618 | + | ||||
| 619 | + | func (_c *MockBrokerService_Subscribe_Call) RunAndReturn(run func() (ports.BrokerSubscription, error)) *MockBrokerService_Subscribe_Call { | |||
| 620 | + | _c.Call.Return(run) | |||
| 621 | + | return _c | |||
| 622 | + | } | |||
| 623 | + | ||||
| 624 | + | // NewMockBrokerSnapshotStore creates a new instance of MockBrokerSnapshotStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 625 | + | // The first argument is typically a *testing.T value. | |||
| 626 | + | func NewMockBrokerSnapshotStore(t interface { | |||
| 627 | + | mock.TestingT | |||
| 628 | + | Cleanup(func()) | |||
| 629 | + | }) *MockBrokerSnapshotStore { | |||
| 630 | + | mock := &MockBrokerSnapshotStore{} | |||
| 631 | + | mock.Mock.Test(t) | |||
| 632 | + | ||||
| 633 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 634 | + | ||||
| 635 | + | return mock | |||
| 636 | + | } | |||
| 637 | + | ||||
| 638 | + | // MockBrokerSnapshotStore is an autogenerated mock type for the BrokerSnapshotStore type | |||
| 639 | + | type MockBrokerSnapshotStore struct { | |||
| 640 | + | mock.Mock | |||
| 641 | + | } | |||
| 642 | + | ||||
| 643 | + | type MockBrokerSnapshotStore_Expecter struct { | |||
| 644 | + | mock *mock.Mock | |||
| 645 | + | } | |||
| 646 | + | ||||
| 647 | + | func (_m *MockBrokerSnapshotStore) EXPECT() *MockBrokerSnapshotStore_Expecter { | |||
| 648 | + | return &MockBrokerSnapshotStore_Expecter{mock: &_m.Mock} | |||
| 649 | + | } | |||
| 650 | + | ||||
| 651 | + | // Load provides a mock function for the type MockBrokerSnapshotStore | |||
| 652 | + | func (_mock *MockBrokerSnapshotStore) Load() (ports.BrokerSnapshot, error) { | |||
| 653 | + | ret := _mock.Called() | |||
| 654 | + | ||||
| 655 | + | if len(ret) == 0 { | |||
| 656 | + | panic("no return value specified for Load") | |||
| 657 | + | } | |||
| 658 | + | ||||
| 659 | + | var r0 ports.BrokerSnapshot | |||
| 660 | + | var r1 error | |||
| 661 | + | if returnFunc, ok := ret.Get(0).(func() (ports.BrokerSnapshot, error)); ok { | |||
| 662 | + | return returnFunc() | |||
| 663 | + | } | |||
| 664 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerSnapshot); ok { | |||
| 665 | + | r0 = returnFunc() | |||
| 666 | + | } else { | |||
| 667 | + | r0 = ret.Get(0).(ports.BrokerSnapshot) | |||
| 668 | + | } | |||
| 669 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |||
| 670 | + | r1 = returnFunc() | |||
| 671 | + | } else { | |||
| 672 | + | r1 = ret.Error(1) | |||
| 673 | + | } | |||
| 674 | + | return r0, r1 | |||
| 675 | + | } | |||
| 676 | + | ||||
| 677 | + | // MockBrokerSnapshotStore_Load_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Load' | |||
| 678 | + | type MockBrokerSnapshotStore_Load_Call struct { | |||
| 679 | + | *mock.Call | |||
| 680 | + | } | |||
| 681 | + | ||||
| 682 | + | // Load is a helper method to define mock.On call | |||
| 683 | + | func (_e *MockBrokerSnapshotStore_Expecter) Load() *MockBrokerSnapshotStore_Load_Call { | |||
| 684 | + | return &MockBrokerSnapshotStore_Load_Call{Call: _e.mock.On("Load")} | |||
| 685 | + | } | |||
| 686 | + | ||||
| 687 | + | func (_c *MockBrokerSnapshotStore_Load_Call) Run(run func()) *MockBrokerSnapshotStore_Load_Call { | |||
| 688 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 689 | + | run() | |||
| 690 | + | }) | |||
| 691 | + | return _c | |||
| 692 | + | } | |||
| 693 | + | ||||
| 694 | + | func (_c *MockBrokerSnapshotStore_Load_Call) Return(brokerSnapshot ports.BrokerSnapshot, err error) *MockBrokerSnapshotStore_Load_Call { | |||
| 695 | + | _c.Call.Return(brokerSnapshot, err) | |||
| 696 | + | return _c | |||
| 697 | + | } | |||
| 698 | + | ||||
| 699 | + | func (_c *MockBrokerSnapshotStore_Load_Call) RunAndReturn(run func() (ports.BrokerSnapshot, error)) *MockBrokerSnapshotStore_Load_Call { | |||
| 700 | + | _c.Call.Return(run) | |||
| 701 | + | return _c | |||
| 702 | + | } | |||
| 703 | + | ||||
| 704 | + | // Store provides a mock function for the type MockBrokerSnapshotStore | |||
| 705 | + | func (_mock *MockBrokerSnapshotStore) Store(brokerSnapshot ports.BrokerSnapshot) error { | |||
| 706 | + | ret := _mock.Called(brokerSnapshot) | |||
| 707 | + | ||||
| 708 | + | if len(ret) == 0 { | |||
| 709 | + | panic("no return value specified for Store") | |||
| 710 | + | } | |||
| 711 | + | ||||
| 712 | + | var r0 error | |||
| 713 | + | if returnFunc, ok := ret.Get(0).(func(ports.BrokerSnapshot) error); ok { | |||
| 714 | + | r0 = returnFunc(brokerSnapshot) | |||
| 715 | + | } else { | |||
| 716 | + | r0 = ret.Error(0) | |||
| 717 | + | } | |||
| 718 | + | return r0 | |||
| 719 | + | } | |||
| 720 | + | ||||
| 721 | + | // MockBrokerSnapshotStore_Store_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Store' | |||
| 722 | + | type MockBrokerSnapshotStore_Store_Call struct { | |||
| 723 | + | *mock.Call | |||
| 724 | + | } | |||
| 725 | + | ||||
| 726 | + | // Store is a helper method to define mock.On call | |||
| 727 | + | // - brokerSnapshot ports.BrokerSnapshot | |||
| 728 | + | func (_e *MockBrokerSnapshotStore_Expecter) Store(brokerSnapshot any) *MockBrokerSnapshotStore_Store_Call { | |||
| 729 | + | return &MockBrokerSnapshotStore_Store_Call{Call: _e.mock.On("Store", brokerSnapshot)} | |||
| 730 | + | } | |||
| 731 | + | ||||
| 732 | + | func (_c *MockBrokerSnapshotStore_Store_Call) Run(run func(brokerSnapshot ports.BrokerSnapshot)) *MockBrokerSnapshotStore_Store_Call { | |||
| 733 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 734 | + | var arg0 ports.BrokerSnapshot | |||
| 735 | + | if args[0] != nil { | |||
| 736 | + | arg0 = args[0].(ports.BrokerSnapshot) | |||
| 737 | + | } | |||
| 738 | + | run( | |||
| 739 | + | arg0, | |||
| 740 | + | ) | |||
| 741 | + | }) | |||
| 742 | + | return _c | |||
| 743 | + | } | |||
| 744 | + | ||||
| 745 | + | func (_c *MockBrokerSnapshotStore_Store_Call) Return(err error) *MockBrokerSnapshotStore_Store_Call { | |||
| 746 | + | _c.Call.Return(err) | |||
| 747 | + | return _c | |||
| 748 | + | } | |||
| 749 | + | ||||
| 750 | + | func (_c *MockBrokerSnapshotStore_Store_Call) RunAndReturn(run func(brokerSnapshot ports.BrokerSnapshot) error) *MockBrokerSnapshotStore_Store_Call { | |||
| 751 | + | _c.Call.Return(run) | |||
| 752 | + | return _c | |||
| 753 | + | } | |||
| 754 | + | ||||
| 755 | + | // NewMockBrokerPhysicalConnection creates a new instance of MockBrokerPhysicalConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 756 | + | // The first argument is typically a *testing.T value. | |||
| 757 | + | func NewMockBrokerPhysicalConnection(t interface { | |||
| 758 | + | mock.TestingT | |||
| 759 | + | Cleanup(func()) | |||
| 760 | + | }) *MockBrokerPhysicalConnection { | |||
| 761 | + | mock := &MockBrokerPhysicalConnection{} | |||
| 762 | + | mock.Mock.Test(t) | |||
| 763 | + | ||||
| 764 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 765 | + | ||||
| 766 | + | return mock | |||
| 767 | + | } | |||
| 768 | + | ||||
| 769 | + | // MockBrokerPhysicalConnection is an autogenerated mock type for the BrokerPhysicalConnection type | |||
| 770 | + | type MockBrokerPhysicalConnection struct { | |||
| 771 | + | mock.Mock | |||
| 772 | + | } | |||
| 773 | + | ||||
| 774 | + | type MockBrokerPhysicalConnection_Expecter struct { | |||
| 775 | + | mock *mock.Mock | |||
| 776 | + | } | |||
| 777 | + | ||||
| 778 | + | func (_m *MockBrokerPhysicalConnection) EXPECT() *MockBrokerPhysicalConnection_Expecter { | |||
| 779 | + | return &MockBrokerPhysicalConnection_Expecter{mock: &_m.Mock} | |||
| 780 | + | } | |||
| 781 | + | ||||
| 782 | + | // Close provides a mock function for the type MockBrokerPhysicalConnection | |||
| 783 | + | func (_mock *MockBrokerPhysicalConnection) Close() error { | |||
| 784 | + | ret := _mock.Called() | |||
| 785 | + | ||||
| 786 | + | if len(ret) == 0 { | |||
| 787 | + | panic("no return value specified for Close") | |||
| 788 | + | } | |||
| 789 | + | ||||
| 790 | + | var r0 error | |||
| 791 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |||
| 792 | + | r0 = returnFunc() | |||
| 793 | + | } else { | |||
| 794 | + | r0 = ret.Error(0) | |||
| 795 | + | } | |||
| 796 | + | return r0 | |||
| 797 | + | } | |||
| 798 | + | ||||
| 799 | + | // MockBrokerPhysicalConnection_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |||
| 800 | + | type MockBrokerPhysicalConnection_Close_Call struct { | |||
| 801 | + | *mock.Call | |||
| 802 | + | } | |||
| 803 | + | ||||
| 804 | + | // Close is a helper method to define mock.On call | |||
| 805 | + | func (_e *MockBrokerPhysicalConnection_Expecter) Close() *MockBrokerPhysicalConnection_Close_Call { | |||
| 806 | + | return &MockBrokerPhysicalConnection_Close_Call{Call: _e.mock.On("Close")} | |||
| 807 | + | } | |||
| 808 | + | ||||
| 809 | + | func (_c *MockBrokerPhysicalConnection_Close_Call) Run(run func()) *MockBrokerPhysicalConnection_Close_Call { | |||
| 810 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 811 | + | run() | |||
| 812 | + | }) | |||
| 813 | + | return _c | |||
| 814 | + | } | |||
| 815 | + | ||||
| 816 | + | func (_c *MockBrokerPhysicalConnection_Close_Call) Return(err error) *MockBrokerPhysicalConnection_Close_Call { | |||
| 817 | + | _c.Call.Return(err) | |||
| 818 | + | return _c | |||
| 819 | + | } | |||
| 820 | + | ||||
| 821 | + | func (_c *MockBrokerPhysicalConnection_Close_Call) RunAndReturn(run func() error) *MockBrokerPhysicalConnection_Close_Call { | |||
| 822 | + | _c.Call.Return(run) | |||
| 823 | + | return _c | |||
| 824 | + | } | |||
| 825 | + | ||||
| 826 | + | // Identity provides a mock function for the type MockBrokerPhysicalConnection | |||
| 827 | + | func (_mock *MockBrokerPhysicalConnection) Identity() ports.BrokerDaemonIdentity { | |||
| 828 | + | ret := _mock.Called() | |||
| 829 | + | ||||
| 830 | + | if len(ret) == 0 { | |||
| 831 | + | panic("no return value specified for Identity") | |||
| 832 | + | } | |||
| 833 | + | ||||
| 834 | + | var r0 ports.BrokerDaemonIdentity | |||
| 835 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerDaemonIdentity); ok { | |||
| 836 | + | r0 = returnFunc() | |||
| 837 | + | } else { | |||
| 838 | + | r0 = ret.Get(0).(ports.BrokerDaemonIdentity) | |||
| 839 | + | } | |||
| 840 | + | return r0 | |||
| 841 | + | } | |||
| 842 | + | ||||
| 843 | + | // MockBrokerPhysicalConnection_Identity_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Identity' | |||
| 844 | + | type MockBrokerPhysicalConnection_Identity_Call struct { | |||
| 845 | + | *mock.Call | |||
| 846 | + | } | |||
| 847 | + | ||||
| 848 | + | // Identity is a helper method to define mock.On call | |||
| 849 | + | func (_e *MockBrokerPhysicalConnection_Expecter) Identity() *MockBrokerPhysicalConnection_Identity_Call { | |||
| 850 | + | return &MockBrokerPhysicalConnection_Identity_Call{Call: _e.mock.On("Identity")} | |||
| 851 | + | } | |||
| 852 | + | ||||
| 853 | + | func (_c *MockBrokerPhysicalConnection_Identity_Call) Run(run func()) *MockBrokerPhysicalConnection_Identity_Call { | |||
| 854 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 855 | + | run() | |||
| 856 | + | }) | |||
| 857 | + | return _c | |||
| 858 | + | } | |||
| 859 | + | ||||
| 860 | + | func (_c *MockBrokerPhysicalConnection_Identity_Call) Return(brokerDaemonIdentity ports.BrokerDaemonIdentity) *MockBrokerPhysicalConnection_Identity_Call { | |||
| 861 | + | _c.Call.Return(brokerDaemonIdentity) | |||
| 862 | + | return _c | |||
| 863 | + | } | |||
| 864 | + | ||||
| 865 | + | func (_c *MockBrokerPhysicalConnection_Identity_Call) RunAndReturn(run func() ports.BrokerDaemonIdentity) *MockBrokerPhysicalConnection_Identity_Call { | |||
| 866 | + | _c.Call.Return(run) | |||
| 867 | + | return _c | |||
| 868 | + | } | |||
| 869 | + | ||||
| 870 | + | // Incarnation provides a mock function for the type MockBrokerPhysicalConnection | |||
| 871 | + | func (_mock *MockBrokerPhysicalConnection) Incarnation() ports.BrokerDaemonIncarnation { | |||
| 872 | + | ret := _mock.Called() | |||
| 873 | + | ||||
| 874 | + | if len(ret) == 0 { | |||
| 875 | + | panic("no return value specified for Incarnation") | |||
| 876 | + | } | |||
| 877 | + | ||||
| 878 | + | var r0 ports.BrokerDaemonIncarnation | |||
| 879 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerDaemonIncarnation); ok { | |||
| 880 | + | r0 = returnFunc() | |||
| 881 | + | } else { | |||
| 882 | + | if ret.Get(0) != nil { | |||
| 883 | + | r0 = ret.Get(0).(ports.BrokerDaemonIncarnation) | |||
| 884 | + | } | |||
| 885 | + | } | |||
| 886 | + | return r0 | |||
| 887 | + | } | |||
| 888 | + | ||||
| 889 | + | // MockBrokerPhysicalConnection_Incarnation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Incarnation' | |||
| 890 | + | type MockBrokerPhysicalConnection_Incarnation_Call struct { | |||
| 891 | + | *mock.Call | |||
| 892 | + | } | |||
| 893 | + | ||||
| 894 | + | // Incarnation is a helper method to define mock.On call | |||
| 895 | + | func (_e *MockBrokerPhysicalConnection_Expecter) Incarnation() *MockBrokerPhysicalConnection_Incarnation_Call { | |||
| 896 | + | return &MockBrokerPhysicalConnection_Incarnation_Call{Call: _e.mock.On("Incarnation")} | |||
| 897 | + | } | |||
| 898 | + | ||||
| 899 | + | func (_c *MockBrokerPhysicalConnection_Incarnation_Call) Run(run func()) *MockBrokerPhysicalConnection_Incarnation_Call { | |||
| 900 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 901 | + | run() | |||
| 902 | + | }) | |||
| 903 | + | return _c | |||
| 904 | + | } | |||
| 905 | + | ||||
| 906 | + | func (_c *MockBrokerPhysicalConnection_Incarnation_Call) Return(brokerDaemonIncarnation ports.BrokerDaemonIncarnation) *MockBrokerPhysicalConnection_Incarnation_Call { | |||
| 907 | + | _c.Call.Return(brokerDaemonIncarnation) | |||
| 908 | + | return _c | |||
| 909 | + | } | |||
| 910 | + | ||||
| 911 | + | func (_c *MockBrokerPhysicalConnection_Incarnation_Call) RunAndReturn(run func() ports.BrokerDaemonIncarnation) *MockBrokerPhysicalConnection_Incarnation_Call { | |||
| 912 | + | _c.Call.Return(run) | |||
| 913 | + | return _c | |||
| 914 | + | } | |||
| 915 | + | ||||
| 916 | + | // OpenStream provides a mock function for the type MockBrokerPhysicalConnection | |||
| 917 | + | func (_mock *MockBrokerPhysicalConnection) OpenStream(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.ClientConnection, error) { | |||
| 918 | + | ret := _mock.Called(ctx, request) | |||
| 919 | + | ||||
| 920 | + | if len(ret) == 0 { | |||
| 921 | + | panic("no return value specified for OpenStream") | |||
| 922 | + | } | |||
| 923 | + | ||||
| 924 | + | var r0 ports.ClientConnection | |||
| 925 | + | var r1 error | |||
| 926 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, ports.BrokerOpenStreamRequest) (ports.ClientConnection, error)); ok { | |||
| 927 | + | return returnFunc(ctx, request) | |||
| 928 | + | } | |||
| 929 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, ports.BrokerOpenStreamRequest) ports.ClientConnection); ok { | |||
| 930 | + | r0 = returnFunc(ctx, request) | |||
| 931 | + | } else { | |||
| 932 | + | if ret.Get(0) != nil { | |||
| 933 | + | r0 = ret.Get(0).(ports.ClientConnection) | |||
| 934 | + | } | |||
| 935 | + | } | |||
| 936 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, ports.BrokerOpenStreamRequest) error); ok { | |||
| 937 | + | r1 = returnFunc(ctx, request) | |||
| 938 | + | } else { | |||
| 939 | + | r1 = ret.Error(1) | |||
| 940 | + | } | |||
| 941 | + | return r0, r1 | |||
| 942 | + | } | |||
| 943 | + | ||||
| 944 | + | // MockBrokerPhysicalConnection_OpenStream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenStream' | |||
| 945 | + | type MockBrokerPhysicalConnection_OpenStream_Call struct { | |||
| 946 | + | *mock.Call | |||
| 947 | + | } | |||
| 948 | + | ||||
| 949 | + | // OpenStream is a helper method to define mock.On call | |||
| 950 | + | // - ctx context.Context | |||
| 951 | + | // - request ports.BrokerOpenStreamRequest | |||
| 952 | + | func (_e *MockBrokerPhysicalConnection_Expecter) OpenStream(ctx any, request any) *MockBrokerPhysicalConnection_OpenStream_Call { | |||
| 953 | + | return &MockBrokerPhysicalConnection_OpenStream_Call{Call: _e.mock.On("OpenStream", ctx, request)} | |||
| 954 | + | } | |||
| 955 | + | ||||
| 956 | + | func (_c *MockBrokerPhysicalConnection_OpenStream_Call) Run(run func(ctx context.Context, request ports.BrokerOpenStreamRequest)) *MockBrokerPhysicalConnection_OpenStream_Call { | |||
| 957 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 958 | + | var arg0 context.Context | |||
| 959 | + | if args[0] != nil { | |||
| 960 | + | arg0 = args[0].(context.Context) | |||
| 961 | + | } | |||
| 962 | + | var arg1 ports.BrokerOpenStreamRequest | |||
| 963 | + | if args[1] != nil { | |||
| 964 | + | arg1 = args[1].(ports.BrokerOpenStreamRequest) | |||
| 965 | + | } | |||
| 966 | + | run( | |||
| 967 | + | arg0, | |||
| 968 | + | arg1, | |||
| 969 | + | ) | |||
| 970 | + | }) | |||
| 971 | + | return _c | |||
| 972 | + | } | |||
| 973 | + | ||||
| 974 | + | func (_c *MockBrokerPhysicalConnection_OpenStream_Call) Return(clientConnection ports.ClientConnection, err error) *MockBrokerPhysicalConnection_OpenStream_Call { | |||
| 975 | + | _c.Call.Return(clientConnection, err) | |||
| 976 | + | return _c | |||
| 977 | + | } | |||
| 978 | + | ||||
| 979 | + | func (_c *MockBrokerPhysicalConnection_OpenStream_Call) RunAndReturn(run func(ctx context.Context, request ports.BrokerOpenStreamRequest) (ports.ClientConnection, error)) *MockBrokerPhysicalConnection_OpenStream_Call { | |||
| 980 | + | _c.Call.Return(run) | |||
| 981 | + | return _c | |||
| 982 | + | } | |||
| 983 | + | ||||
| 984 | + | // Policy provides a mock function for the type MockBrokerPhysicalConnection | |||
| 985 | + | func (_mock *MockBrokerPhysicalConnection) Policy() ports.BrokerPolicy { | |||
| 986 | + | ret := _mock.Called() | |||
| 987 | + | ||||
| 988 | + | if len(ret) == 0 { | |||
| 989 | + | panic("no return value specified for Policy") | |||
| 990 | + | } | |||
| 991 | + | ||||
| 992 | + | var r0 ports.BrokerPolicy | |||
| 993 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerPolicy); ok { | |||
| 994 | + | r0 = returnFunc() | |||
| 995 | + | } else { | |||
| 996 | + | r0 = ret.Get(0).(ports.BrokerPolicy) | |||
| 997 | + | } | |||
| 998 | + | return r0 | |||
| 999 | + | } | |||
| 1000 | + | ||||
| 1001 | + | // MockBrokerPhysicalConnection_Policy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Policy' | |||
| 1002 | + | type MockBrokerPhysicalConnection_Policy_Call struct { | |||
| 1003 | + | *mock.Call | |||
| 1004 | + | } | |||
| 1005 | + | ||||
| 1006 | + | // Policy is a helper method to define mock.On call | |||
| 1007 | + | func (_e *MockBrokerPhysicalConnection_Expecter) Policy() *MockBrokerPhysicalConnection_Policy_Call { | |||
| 1008 | + | return &MockBrokerPhysicalConnection_Policy_Call{Call: _e.mock.On("Policy")} | |||
| 1009 | + | } | |||
| 1010 | + | ||||
| 1011 | + | func (_c *MockBrokerPhysicalConnection_Policy_Call) Run(run func()) *MockBrokerPhysicalConnection_Policy_Call { | |||
| 1012 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 1013 | + | run() | |||
| 1014 | + | }) | |||
| 1015 | + | return _c | |||
| 1016 | + | } | |||
| 1017 | + | ||||
| 1018 | + | func (_c *MockBrokerPhysicalConnection_Policy_Call) Return(brokerPolicy ports.BrokerPolicy) *MockBrokerPhysicalConnection_Policy_Call { | |||
| 1019 | + | _c.Call.Return(brokerPolicy) | |||
| 1020 | + | return _c | |||
| 1021 | + | } | |||
| 1022 | + | ||||
| 1023 | + | func (_c *MockBrokerPhysicalConnection_Policy_Call) RunAndReturn(run func() ports.BrokerPolicy) *MockBrokerPhysicalConnection_Policy_Call { | |||
| 1024 | + | _c.Call.Return(run) | |||
| 1025 | + | return _c | |||
| 1026 | + | } | |||
| 1027 | + | ||||
| 1028 | + | // NewMockBrokerEndpointConnector creates a new instance of MockBrokerEndpointConnector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 1029 | + | // The first argument is typically a *testing.T value. | |||
| 1030 | + | func NewMockBrokerEndpointConnector(t interface { | |||
| 1031 | + | mock.TestingT | |||
| 1032 | + | Cleanup(func()) | |||
| 1033 | + | }) *MockBrokerEndpointConnector { | |||
| 1034 | + | mock := &MockBrokerEndpointConnector{} | |||
| 1035 | + | mock.Mock.Test(t) | |||
| 1036 | + | ||||
| 1037 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 1038 | + | ||||
| 1039 | + | return mock | |||
| 1040 | + | } | |||
| 1041 | + | ||||
| 1042 | + | // MockBrokerEndpointConnector is an autogenerated mock type for the BrokerEndpointConnector type | |||
| 1043 | + | type MockBrokerEndpointConnector struct { | |||
| 1044 | + | mock.Mock | |||
| 1045 | + | } | |||
| 1046 | + | ||||
| 1047 | + | type MockBrokerEndpointConnector_Expecter struct { | |||
| 1048 | + | mock *mock.Mock | |||
| 1049 | + | } | |||
| 1050 | + | ||||
| 1051 | + | func (_m *MockBrokerEndpointConnector) EXPECT() *MockBrokerEndpointConnector_Expecter { | |||
| 1052 | + | return &MockBrokerEndpointConnector_Expecter{mock: &_m.Mock} | |||
| 1053 | + | } | |||
| 1054 | + | ||||
| 1055 | + | // Connect provides a mock function for the type MockBrokerEndpointConnector | |||
| 1056 | + | func (_mock *MockBrokerEndpointConnector) Connect(ctx context.Context, endpoint string, policy ports.BrokerPolicy) (ports.BrokerPhysicalConnection, error) { | |||
| 1057 | + | ret := _mock.Called(ctx, endpoint, policy) | |||
| 1058 | + | ||||
| 1059 | + | if len(ret) == 0 { | |||
| 1060 | + | panic("no return value specified for Connect") | |||
| 1061 | + | } | |||
| 1062 | + | ||||
| 1063 | + | var r0 ports.BrokerPhysicalConnection | |||
| 1064 | + | var r1 error | |||
| 1065 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.BrokerPolicy) (ports.BrokerPhysicalConnection, error)); ok { | |||
| 1066 | + | return returnFunc(ctx, endpoint, policy) | |||
| 1067 | + | } | |||
| 1068 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, ports.BrokerPolicy) ports.BrokerPhysicalConnection); ok { | |||
| 1069 | + | r0 = returnFunc(ctx, endpoint, policy) | |||
| 1070 | + | } else { | |||
| 1071 | + | if ret.Get(0) != nil { | |||
| 1072 | + | r0 = ret.Get(0).(ports.BrokerPhysicalConnection) | |||
| 1073 | + | } | |||
| 1074 | + | } | |||
| 1075 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string, ports.BrokerPolicy) error); ok { | |||
| 1076 | + | r1 = returnFunc(ctx, endpoint, policy) | |||
| 1077 | + | } else { | |||
| 1078 | + | r1 = ret.Error(1) | |||
| 1079 | + | } | |||
| 1080 | + | return r0, r1 | |||
| 1081 | + | } | |||
| 1082 | + | ||||
| 1083 | + | // MockBrokerEndpointConnector_Connect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Connect' | |||
| 1084 | + | type MockBrokerEndpointConnector_Connect_Call struct { | |||
| 1085 | + | *mock.Call | |||
| 1086 | + | } | |||
| 1087 | + | ||||
| 1088 | + | // Connect is a helper method to define mock.On call | |||
| 1089 | + | // - ctx context.Context | |||
| 1090 | + | // - endpoint string | |||
| 1091 | + | // - policy ports.BrokerPolicy | |||
| 1092 | + | func (_e *MockBrokerEndpointConnector_Expecter) Connect(ctx any, endpoint any, policy any) *MockBrokerEndpointConnector_Connect_Call { | |||
| 1093 | + | return &MockBrokerEndpointConnector_Connect_Call{Call: _e.mock.On("Connect", ctx, endpoint, policy)} | |||
| 1094 | + | } | |||
| 1095 | + | ||||
| 1096 | + | func (_c *MockBrokerEndpointConnector_Connect_Call) Run(run func(ctx context.Context, endpoint string, policy ports.BrokerPolicy)) *MockBrokerEndpointConnector_Connect_Call { | |||
| 1097 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 1098 | + | var arg0 context.Context | |||
| 1099 | + | if args[0] != nil { | |||
| 1100 | + | arg0 = args[0].(context.Context) | |||
| 1101 | + | } | |||
| 1102 | + | var arg1 string | |||
| 1103 | + | if args[1] != nil { | |||
| 1104 | + | arg1 = args[1].(string) | |||
| 1105 | + | } | |||
| 1106 | + | var arg2 ports.BrokerPolicy | |||
| 1107 | + | if args[2] != nil { | |||
| 1108 | + | arg2 = args[2].(ports.BrokerPolicy) | |||
| 1109 | + | } | |||
| 1110 | + | run( | |||
| 1111 | + | arg0, | |||
| 1112 | + | arg1, | |||
| 1113 | + | arg2, | |||
| 1114 | + | ) | |||
| 1115 | + | }) | |||
| 1116 | + | return _c | |||
| 1117 | + | } | |||
| 1118 | + | ||||
| 1119 | + | func (_c *MockBrokerEndpointConnector_Connect_Call) Return(brokerPhysicalConnection ports.BrokerPhysicalConnection, err error) *MockBrokerEndpointConnector_Connect_Call { | |||
| 1120 | + | _c.Call.Return(brokerPhysicalConnection, err) | |||
| 1121 | + | return _c | |||
| 1122 | + | } | |||
| 1123 | + | ||||
| 1124 | + | func (_c *MockBrokerEndpointConnector_Connect_Call) RunAndReturn(run func(ctx context.Context, endpoint string, policy ports.BrokerPolicy) (ports.BrokerPhysicalConnection, error)) *MockBrokerEndpointConnector_Connect_Call { | |||
| 1125 | + | _c.Call.Return(run) | |||
| 1126 | + | return _c | |||
| 1127 | + | } | |||
| 1128 | + | ||||
| 1129 | + | // NewMockBrokerListener creates a new instance of MockBrokerListener. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 1130 | + | // The first argument is typically a *testing.T value. | |||
| 1131 | + | func NewMockBrokerListener(t interface { | |||
| 1132 | + | mock.TestingT | |||
| 1133 | + | Cleanup(func()) | |||
| 1134 | + | }) *MockBrokerListener { | |||
| 1135 | + | mock := &MockBrokerListener{} | |||
| 1136 | + | mock.Mock.Test(t) | |||
| 1137 | + | ||||
| 1138 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 1139 | + | ||||
| 1140 | + | return mock | |||
| 1141 | + | } | |||
| 1142 | + | ||||
| 1143 | + | // MockBrokerListener is an autogenerated mock type for the BrokerListener type | |||
| 1144 | + | type MockBrokerListener struct { | |||
| 1145 | + | mock.Mock | |||
| 1146 | + | } | |||
| 1147 | + | ||||
| 1148 | + | type MockBrokerListener_Expecter struct { | |||
| 1149 | + | mock *mock.Mock | |||
| 1150 | + | } | |||
| 1151 | + | ||||
| 1152 | + | func (_m *MockBrokerListener) EXPECT() *MockBrokerListener_Expecter { | |||
| 1153 | + | return &MockBrokerListener_Expecter{mock: &_m.Mock} | |||
| 1154 | + | } | |||
| 1155 | + | ||||
| 1156 | + | // Accept provides a mock function for the type MockBrokerListener | |||
| 1157 | + | func (_mock *MockBrokerListener) Accept() (ports.BrokerService, error) { | |||
| 1158 | + | ret := _mock.Called() | |||
| 1159 | + | ||||
| 1160 | + | if len(ret) == 0 { | |||
| 1161 | + | panic("no return value specified for Accept") | |||
| 1162 | + | } | |||
| 1163 | + | ||||
| 1164 | + | var r0 ports.BrokerService | |||
| 1165 | + | var r1 error | |||
| 1166 | + | if returnFunc, ok := ret.Get(0).(func() (ports.BrokerService, error)); ok { | |||
| 1167 | + | return returnFunc() | |||
| 1168 | + | } | |||
| 1169 | + | if returnFunc, ok := ret.Get(0).(func() ports.BrokerService); ok { | |||
| 1170 | + | r0 = returnFunc() | |||
| 1171 | + | } else { | |||
| 1172 | + | if ret.Get(0) != nil { | |||
| 1173 | + | r0 = ret.Get(0).(ports.BrokerService) | |||
| 1174 | + | } | |||
| 1175 | + | } | |||
| 1176 | + | if returnFunc, ok := ret.Get(1).(func() error); ok { | |||
| 1177 | + | r1 = returnFunc() | |||
| 1178 | + | } else { | |||
| 1179 | + | r1 = ret.Error(1) | |||
| 1180 | + | } | |||
| 1181 | + | return r0, r1 | |||
| 1182 | + | } | |||
| 1183 | + | ||||
| 1184 | + | // MockBrokerListener_Accept_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Accept' | |||
| 1185 | + | type MockBrokerListener_Accept_Call struct { | |||
| 1186 | + | *mock.Call | |||
| 1187 | + | } | |||
| 1188 | + | ||||
| 1189 | + | // Accept is a helper method to define mock.On call | |||
| 1190 | + | func (_e *MockBrokerListener_Expecter) Accept() *MockBrokerListener_Accept_Call { | |||
| 1191 | + | return &MockBrokerListener_Accept_Call{Call: _e.mock.On("Accept")} | |||
| 1192 | + | } | |||
| 1193 | + | ||||
| 1194 | + | func (_c *MockBrokerListener_Accept_Call) Run(run func()) *MockBrokerListener_Accept_Call { | |||
| 1195 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 1196 | + | run() | |||
| 1197 | + | }) | |||
| 1198 | + | return _c | |||
| 1199 | + | } | |||
| 1200 | + | ||||
| 1201 | + | func (_c *MockBrokerListener_Accept_Call) Return(brokerService ports.BrokerService, err error) *MockBrokerListener_Accept_Call { | |||
| 1202 | + | _c.Call.Return(brokerService, err) | |||
| 1203 | + | return _c | |||
| 1204 | + | } | |||
| 1205 | + | ||||
| 1206 | + | func (_c *MockBrokerListener_Accept_Call) RunAndReturn(run func() (ports.BrokerService, error)) *MockBrokerListener_Accept_Call { | |||
| 1207 | + | _c.Call.Return(run) | |||
| 1208 | + | return _c | |||
| 1209 | + | } | |||
| 1210 | + | ||||
| 1211 | + | // Addr provides a mock function for the type MockBrokerListener | |||
| 1212 | + | func (_mock *MockBrokerListener) Addr() string { | |||
| 1213 | + | ret := _mock.Called() | |||
| 1214 | + | ||||
| 1215 | + | if len(ret) == 0 { | |||
| 1216 | + | panic("no return value specified for Addr") | |||
| 1217 | + | } | |||
| 1218 | + | ||||
| 1219 | + | var r0 string | |||
| 1220 | + | if returnFunc, ok := ret.Get(0).(func() string); ok { | |||
| 1221 | + | r0 = returnFunc() | |||
| 1222 | + | } else { | |||
| 1223 | + | r0 = ret.Get(0).(string) | |||
| 1224 | + | } | |||
| 1225 | + | return r0 | |||
| 1226 | + | } | |||
| 1227 | + | ||||
| 1228 | + | // MockBrokerListener_Addr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Addr' | |||
| 1229 | + | type MockBrokerListener_Addr_Call struct { | |||
| 1230 | + | *mock.Call | |||
| 1231 | + | } | |||
| 1232 | + | ||||
| 1233 | + | // Addr is a helper method to define mock.On call | |||
| 1234 | + | func (_e *MockBrokerListener_Expecter) Addr() *MockBrokerListener_Addr_Call { | |||
| 1235 | + | return &MockBrokerListener_Addr_Call{Call: _e.mock.On("Addr")} | |||
| 1236 | + | } | |||
| 1237 | + | ||||
| 1238 | + | func (_c *MockBrokerListener_Addr_Call) Run(run func()) *MockBrokerListener_Addr_Call { | |||
| 1239 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 1240 | + | run() | |||
| 1241 | + | }) | |||
| 1242 | + | return _c | |||
| 1243 | + | } | |||
| 1244 | + | ||||
| 1245 | + | func (_c *MockBrokerListener_Addr_Call) Return(s string) *MockBrokerListener_Addr_Call { | |||
| 1246 | + | _c.Call.Return(s) | |||
| 1247 | + | return _c | |||
| 1248 | + | } | |||
| 1249 | + | ||||
| 1250 | + | func (_c *MockBrokerListener_Addr_Call) RunAndReturn(run func() string) *MockBrokerListener_Addr_Call { | |||
| 1251 | + | _c.Call.Return(run) | |||
| 1252 | + | return _c | |||
| 1253 | + | } | |||
| 1254 | + | ||||
| 1255 | + | // Close provides a mock function for the type MockBrokerListener | |||
| 1256 | + | func (_mock *MockBrokerListener) Close() error { | |||
| 1257 | + | ret := _mock.Called() | |||
| 1258 | + | ||||
| 1259 | + | if len(ret) == 0 { | |||
| 1260 | + | panic("no return value specified for Close") | |||
| 1261 | + | } | |||
| 1262 | + | ||||
| 1263 | + | var r0 error | |||
| 1264 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |||
| 1265 | + | r0 = returnFunc() | |||
| 1266 | + | } else { | |||
| 1267 | + | r0 = ret.Error(0) | |||
| 1268 | + | } | |||
| 1269 | + | return r0 | |||
| 1270 | + | } | |||
| 1271 | + | ||||
| 1272 | + | // MockBrokerListener_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |||
| 1273 | + | type MockBrokerListener_Close_Call struct { | |||
| 1274 | + | *mock.Call | |||
| 1275 | + | } | |||
| 1276 | + | ||||
| 1277 | + | // Close is a helper method to define mock.On call | |||
| 1278 | + | func (_e *MockBrokerListener_Expecter) Close() *MockBrokerListener_Close_Call { | |||
| 1279 | + | return &MockBrokerListener_Close_Call{Call: _e.mock.On("Close")} | |||
| 1280 | + | } | |||
| 1281 | + | ||||
| 1282 | + | func (_c *MockBrokerListener_Close_Call) Run(run func()) *MockBrokerListener_Close_Call { | |||
| 1283 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 1284 | + | run() | |||
| 1285 | + | }) | |||
| 1286 | + | return _c | |||
| 1287 | + | } | |||
| 1288 | + | ||||
| 1289 | + | func (_c *MockBrokerListener_Close_Call) Return(err error) *MockBrokerListener_Close_Call { | |||
| 1290 | + | _c.Call.Return(err) | |||
| 1291 | + | return _c | |||
| 1292 | + | } | |||
| 1293 | + | ||||
| 1294 | + | func (_c *MockBrokerListener_Close_Call) RunAndReturn(run func() error) *MockBrokerListener_Close_Call { | |||
| 1295 | + | _c.Call.Return(run) | |||
| 1296 | + | return _c | |||
| 1297 | + | } | |||
A internal/usecase/broker/doc.go +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | // Package broker implements vev's per-user connection broker. | |
| 2 | + | // | |
| 3 | + | // The broker is the machine-wide owner of connectivity: configured hosts, | |
| 4 | + | // daemon reachability and catalogue snapshots, pooled physical transports, | |
| 5 | + | // and multiplexed logical streams. It never owns picker UI, sessions, | |
| 6 | + | // PTYs, terminal geometry, or attachment semantics. See ADR 001. | |
| 7 | + | package broker | |
| @@ -0,0 +1,7 @@ | |||||
| 1 | + | // Package broker implements vev's per-user connection broker. | |||
| 2 | + | // | |||
| 3 | + | // The broker is the machine-wide owner of connectivity: configured hosts, | |||
| 4 | + | // daemon reachability and catalogue snapshots, pooled physical transports, | |||
| 5 | + | // and multiplexed logical streams. It never owns picker UI, sessions, | |||
| 6 | + | // PTYs, terminal geometry, or attachment semantics. See ADR 001. | |||
| 7 | + | package broker | |||