Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions async-config-poll/config-stub/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
// - GET /v1/buckets/app-config?watch=true&version=N -> long-poll: returns the
// NEXT version (N+1), simulating a config change on each watch poll.
//
// By default a watch poll returns immediately (the periodic-poller scenario).
// Set POLL_HOLD_SECONDS>0 to model a real long-poll with a server timeout: the
// watch=true request is HELD open that long before delivering the next version
// (the httpPoll scenario), so Keploy records its open-duration as pollDurationMs.
//
// It is hit only during `keploy record`. At replay time Keploy serves the
// recorded responses instead, so this stub does not need to be running.
package main
Expand All @@ -13,18 +18,42 @@ import (
"encoding/json"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)

// pollHold is the long-poll server timeout: a watch=true request is held open
// this long before delivering the next version. Default 0 (respond
// immediately); override with POLL_HOLD_SECONDS.
func pollHold() time.Duration {
if s := os.Getenv("POLL_HOLD_SECONDS"); s != "" {
if n, err := strconv.Atoi(s); err == nil {
return time.Duration(n) * time.Second
}
}
return 0
}

func main() {
hold := pollHold()
http.HandleFunc("/v1/buckets/", func(w http.ResponseWriter, r *http.Request) {
name := strings.TrimPrefix(r.URL.Path, "/v1/buckets/")
q := r.URL.Query()

version := 1
if q.Get("watch") == "true" {
cur, _ := strconv.Atoi(q.Get("version"))
if hold > 0 {
// Long-poll: hold the connection open until the server timeout,
// then deliver the next version. Abort if the client disconnects.
select {
case <-time.After(hold):
case <-r.Context().Done():
return
}
}
Comment on lines +48 to +56
version = cur + 1 // deliver the next version -> a "change" per poll
}

Expand Down
26 changes: 26 additions & 0 deletions async-config-poll/keploy-httppoll.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Keploy config for the async-config-poll sample — httpPoll scenario.
#
# Identical to keploy.yml except the async lane's type is `httpPoll` instead of
# `http`. `httpPoll` marks the config-watch egress as a long-poll: at record
# Keploy stamps the mock kind `HttpPoll` and captures its open-duration
# (pollDurationMs); at replay the async engine HOLDS the poll until its resolve
# testcase and then serves it (verdict `held`), instead of serving it as soon as
# the request arrives. Paired with POLL_HOLD_SECONDS on the config-stub and
# WATCH_ONCE on the app so the recording holds a single server-timeout long-poll.
#
# Lane "config-watch":
# - match.pathRegex : only the /v1/buckets/app-config endpoint
# - matchQuery.watch : "true" -> only the background watch polls (the
# one-time boot "get current version" call uses
# watch=false and stays an ordinary blocking mock)
# - volatileParams : ["version"] -> the version query param changes every
# poll, so it is treated as shape-noise, not a mismatch
async:
lanes:
- name: config-watch
type: httpPoll
match:
pathRegex: "^/v1/buckets/app-config$"
matchQuery:
watch: "true"
volatileParams: ["version"]
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public void init() {
}

private void startWatchPoller() {
// WATCH_ONCE opens exactly ONE watch poll then stops the daemon. The
// httpPoll scenario uses it so the whole recording holds a single long
// poll (a server-timeout long-poll) rather than a stream of them; the
// default (unset) keeps the periodic-poller behavior.
final boolean watchOnce = "true".equalsIgnoreCase(System.getenv("WATCH_ONCE"));
Thread t = new Thread(() -> {
while (watching) {
try {
Expand All @@ -88,6 +93,9 @@ private void startWatchPoller() {
// the exception so a stack trace is available under DEBUG.
log.debug("config watch poll failed", e);
}
if (watchOnce) {
break; // single long-poll connection for the httpPoll scenario
}
Comment on lines +96 to +98
}
}, "config-watch-poller");
t.setDaemon(true);
Expand Down
Loading