-
Notifications
You must be signed in to change notification settings - Fork 31
fix: satisfy SDK compliance harness 0.8.0 #200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| name: SDK Compliance Tests | ||
|
|
||
| permissions: | ||
| contents: read | ||
| packages: read | ||
| pull-requests: write | ||
|
|
||
| on: | ||
| pull_request: | ||
| push: | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| compliance: | ||
| name: PostHog SDK compliance tests | ||
| uses: PostHog/posthog-sdk-test-harness/.github/workflows/test-sdk-action.yml@be8b8d5a3f94a249659844e94832e874f049c1e4 | ||
| with: | ||
| adapter-dockerfile: "sdk_compliance_adapter/Dockerfile" | ||
| adapter-context: "." | ||
| test-harness-version: "0.8.0" |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,9 @@ | |||||||||||||||
| require 'net/http' | ||||||||||||||||
| require 'net/https' | ||||||||||||||||
| require 'json' | ||||||||||||||||
| require 'stringio' | ||||||||||||||||
| require 'time' | ||||||||||||||||
| require 'zlib' | ||||||||||||||||
|
|
||||||||||||||||
| module PostHog | ||||||||||||||||
| # HTTP transport used by the SDK workers. | ||||||||||||||||
|
|
@@ -40,10 +43,12 @@ def initialize(options = {}) | |||||||||||||||
| options[:port] = options[:port].nil? ? PORT : options[:port] | ||||||||||||||||
| options[:ssl] = options[:ssl].nil? ? SSL : options[:ssl] | ||||||||||||||||
|
|
||||||||||||||||
| @headers = options[:headers] || HEADERS | ||||||||||||||||
| @headers = (options[:headers] || HEADERS).dup | ||||||||||||||||
| @path = options[:path] || PATH | ||||||||||||||||
| @retries = options[:retries] || RETRIES | ||||||||||||||||
| @backoff_policy = options[:backoff_policy] || PostHog::BackoffPolicy.new | ||||||||||||||||
| @gzip = options[:gzip] == true | ||||||||||||||||
| @last_retry_after = nil | ||||||||||||||||
|
|
||||||||||||||||
| http = Net::HTTP.new(options[:host], options[:port]) | ||||||||||||||||
| http.use_ssl = options[:ssl] | ||||||||||||||||
|
|
@@ -100,10 +105,8 @@ def shutdown | |||||||||||||||
| private | ||||||||||||||||
|
|
||||||||||||||||
| def should_retry_request?(status_code, body) | ||||||||||||||||
| if status_code >= 500 | ||||||||||||||||
| true # Server error | ||||||||||||||||
| elsif status_code == 429 # rubocop:disable Lint/DuplicateBranch | ||||||||||||||||
| true # Rate limited | ||||||||||||||||
| if status_code >= 500 || [408, 429].include?(status_code) | ||||||||||||||||
| true # Server error, request timeout, or rate limited | ||||||||||||||||
| elsif status_code >= 400 | ||||||||||||||||
| logger.error(body) | ||||||||||||||||
| false # Client error. Do not retry, but log | ||||||||||||||||
|
|
@@ -133,18 +136,49 @@ def retry_with_backoff(retries_remaining, &block) | |||||||||||||||
|
|
||||||||||||||||
| if should_retry && (retries_remaining > 1) | ||||||||||||||||
| logger.debug("Retrying request, #{retries_remaining} retries left") | ||||||||||||||||
| sleep(@backoff_policy.next_interval.to_f / 1000) | ||||||||||||||||
| sleep(retry_delay_seconds) | ||||||||||||||||
| retry_with_backoff(retries_remaining - 1, &block) | ||||||||||||||||
| else | ||||||||||||||||
| [result, caught_exception] | ||||||||||||||||
| end | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| def retry_delay_seconds | ||||||||||||||||
| retry_after = parse_retry_after(@last_retry_after) | ||||||||||||||||
| @last_retry_after = nil | ||||||||||||||||
| return retry_after if retry_after | ||||||||||||||||
|
|
||||||||||||||||
| @backoff_policy.next_interval.to_f / 1000 | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| def parse_retry_after(value) | ||||||||||||||||
| return nil if value.nil? || value.empty? | ||||||||||||||||
|
|
||||||||||||||||
| seconds = Float(value, exception: false) | ||||||||||||||||
| return seconds if seconds&.positive? | ||||||||||||||||
|
|
||||||||||||||||
| parsed_time = Time.httpdate(value) | ||||||||||||||||
| delay = parsed_time - Time.now | ||||||||||||||||
| delay.positive? ? delay : nil | ||||||||||||||||
| rescue ArgumentError | ||||||||||||||||
| nil | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| def gzip(payload) | ||||||||||||||||
| io = StringIO.new | ||||||||||||||||
| Zlib::GzipWriter.wrap(io) { |gzip| gzip.write(payload) } | ||||||||||||||||
| io.string | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| # Sends a request for the batch, returns [status_code, body] | ||||||||||||||||
| def send_request(api_key, batch) | ||||||||||||||||
| payload = JSON.generate(api_key: api_key, batch: batch) | ||||||||||||||||
|
Comment on lines
173
to
175
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| request = Net::HTTP::Post.new(@path, @headers) | ||||||||||||||||
| if @gzip | ||||||||||||||||
| payload = gzip(payload) | ||||||||||||||||
| request['Content-Encoding'] = 'gzip' | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| if self.class.stub | ||||||||||||||||
| logger.debug "stubbed request to #{@path}: " \ | ||||||||||||||||
|
|
@@ -155,6 +189,7 @@ def send_request(api_key, batch) | |||||||||||||||
| @http_mutex.synchronize do | ||||||||||||||||
| @http.start unless @http.started? # Maintain a persistent connection | ||||||||||||||||
| response = @http.request(request, payload) | ||||||||||||||||
| @last_retry_after = response['Retry-After'] | ||||||||||||||||
| [response.code.to_i, response.body] | ||||||||||||||||
| end | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| FROM ruby:3.3-slim | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| RUN gem install concurrent-ruby --no-document | ||
|
|
||
| COPY lib/ /app/lib/ | ||
| COPY sdk_compliance_adapter/adapter.rb /app/adapter.rb | ||
|
|
||
| ENV RUBYLIB=/app/lib | ||
|
|
||
| EXPOSE 8080 | ||
|
|
||
| CMD ["ruby", "/app/adapter.rb"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retry-After: 0means "retry immediately", but0.0.positive?returns false, so the method falls through to the HTTP-date branch, fails to parse "0", and returnsnil. That causes the caller to use the backoff policy instead of obeying the header. Usingseconds&.>=(0)matches the semantics of the header spec (any non-negative number is a valid delay, including zero).