StoreMocks stores the filtered and unfiltered mocks for a client ID. Unification (Phase 1): every mock is run through DeriveLifetime on entry so TestModeInfo.Lifetime is populated before any matcher reads it. This is a second safety-net — the mockdb disk loader already derives at load time, but Sto
(ctx context.Context, filtered []*models.Mock, unfiltered []*models.Mock)
| 639 | // deep copy here unless a concrete mutation-safety regression lands |
| 640 | // first. |
| 641 | func (a *Agent) StoreMocks(ctx context.Context, filtered []*models.Mock, unfiltered []*models.Mock) error { |
| 642 | storage := &ClientMockStorage{ |
| 643 | filtered: make([]*models.Mock, len(filtered)), |
| 644 | unfiltered: make([]*models.Mock, len(unfiltered)), |
| 645 | } |
| 646 | |
| 647 | // Shallow copy the slices — only the outer backing array is |
| 648 | // duplicated, the *models.Mock pointers are shared with the |
| 649 | // caller's slices. This is INTENTIONAL and load-bearing: matchers |
| 650 | // look up mocks via pointer identity in MockManager's trees and |
| 651 | // per-connID pools, and HitCount/Lifetime are bumped on the shared |
| 652 | // Mock object so observability is consistent across the stack (see |
| 653 | // the caveat block before this function for the full rationale). |
| 654 | // Do NOT switch to a deep copy without coordinated updates at |
| 655 | // every downstream site. |
| 656 | copy(storage.filtered, filtered) |
| 657 | copy(storage.unfiltered, unfiltered) |
| 658 | |
| 659 | // Derive Lifetime once per mock before they enter the runtime pool. |
| 660 | // Idempotent: DeriveLifetime short-circuits when the Lifetime |
| 661 | // field is already set (i.e. the disk loader has already run), |
| 662 | // so re-deriving here is safe and never double-counts the |
| 663 | // legacyKindFallbackFires telemetry. |
| 664 | for _, m := range storage.filtered { |
| 665 | if m != nil { |
| 666 | m.DeriveLifetime() |
| 667 | } |
| 668 | } |
| 669 | for _, m := range storage.unfiltered { |
| 670 | if m != nil { |
| 671 | m.DeriveLifetime() |
| 672 | } |
| 673 | } |
| 674 | |
| 675 | a.clientMocks.Store(uint64(0), storage) |
| 676 | |
| 677 | // Compute the freeze anchor as the earliest ReqTimestampMock across the |
| 678 | // stored mocks and forward it to AgentHooks. The hook implementation |
| 679 | // decides whether to apply it (a no-op when freezeTime is off). Doing |
| 680 | // this here, alongside the StoreMocks write, guarantees the anchor is |
| 681 | // known to the hook before BeforeTestRun fires — i.e. before the user |
| 682 | // app's first datetime.now() call — which is what closes the bootstrap |
| 683 | // gap that lets boto3 / JWT libs see "now > recorded Expiration" and |
| 684 | // kill the worker. |
| 685 | if anchor := earliestReqTimestamp(storage.filtered, storage.unfiltered); !anchor.IsZero() { |
| 686 | if err := ActiveHooks.SetFreezeAnchor(ctx, anchor); err != nil { |
| 687 | a.logger.Warn("SetFreezeAnchor hook returned error", |
| 688 | zap.Error(err), zap.Time("anchor", anchor)) |
| 689 | } |
| 690 | } |
| 691 | |
| 692 | a.logger.Debug("Successfully stored mocks for client") |
| 693 | return nil |
| 694 | } |
| 695 | |
| 696 | // earliestReqTimestamp returns the earliest non-zero ReqTimestampMock seen |
| 697 | // across the supplied mock slices, or a zero time.Time if none are set. |
nothing calls this directly
no test coverage detected