createGenericMocksAsync reads captured data from both channels and creates mock entries based on request-response alternation. Runs in a background goroutine — never blocks the forwarding path. Exchange-boundary detection: a request-response pair is flushed to the syncMock buffer the instant the fi
(ctx context.Context, logger *zap.Logger, clientCh, destCh <-chan []byte)
| 144 | // therefore one io.Copy chunk. A future protocol-aware parser should |
| 145 | // supersede generic where framing matters. |
| 146 | func createGenericMocksAsync(ctx context.Context, logger *zap.Logger, clientCh, destCh <-chan []byte) { |
| 147 | var genericRequests []models.Payload |
| 148 | var genericResponses []models.Payload |
| 149 | prevChunkWasReq := true // first chunk is always a request (initial reqBuf) |
| 150 | reqTimestampMock := models.CapturedReqTime(ctx) |
| 151 | var resTimestampMock time.Time |
| 152 | |
| 153 | flushMock := func() { |
| 154 | if len(genericRequests) == 0 || len(genericResponses) == 0 { |
| 155 | return |
| 156 | } |
| 157 | metadata := make(map[string]string) |
| 158 | metadata["type"] = "config" |
| 159 | if connID, ok := ctx.Value(models.ClientConnectionIDKey).(string); ok { |
| 160 | metadata["connID"] = connID |
| 161 | } |
| 162 | mock := &models.Mock{ |
| 163 | Version: models.GetVersion(), |
| 164 | Name: "mocks", |
| 165 | Kind: models.GENERIC, |
| 166 | Spec: models.MockSpec{ |
| 167 | GenericRequests: genericRequests, |
| 168 | GenericResponses: genericResponses, |
| 169 | ReqTimestampMock: reqTimestampMock, |
| 170 | ResTimestampMock: resTimestampMock, |
| 171 | Metadata: metadata, |
| 172 | }, |
| 173 | // Generic TCP has no well-defined protocol for the recorder to |
| 174 | // classify commands against; the legacy recorder tags every |
| 175 | // exchange Metadata["type"]="config" which DeriveLifetime |
| 176 | // previously resolved to LifetimeSession. Stamp that explicitly |
| 177 | // so the filter layer's authoritative routing (Lifetime-first, |
| 178 | // metadata.type fallback) short-circuits on a single enum |
| 179 | // compare — no map probe, no kind fallback. |
| 180 | TestModeInfo: models.TestModeInfo{ |
| 181 | Lifetime: models.LifetimeSession, |
| 182 | LifetimeDerived: true, |
| 183 | }, |
| 184 | } |
| 185 | if mgr := syncMock.FromContextOrGlobal(ctx); mgr != nil { |
| 186 | mgr.AddMock(mock) |
| 187 | } |
| 188 | genericRequests = nil |
| 189 | genericResponses = nil |
| 190 | } |
| 191 | |
| 192 | for clientCh != nil || destCh != nil { |
| 193 | select { |
| 194 | case <-ctx.Done(): |
| 195 | flushMock() |
| 196 | return |
| 197 | case buf, ok := <-clientCh: |
| 198 | if !ok { |
| 199 | clientCh = nil |
| 200 | continue |
| 201 | } |
| 202 | // Back-stop for the rare case where the previous completed |
| 203 | // request/response exchange was not flushed when its first |
no test coverage detected