Skip to content

Commit

Permalink
add skippable tests api client
Browse files Browse the repository at this point in the history
  • Loading branch information
anmarchenko committed Apr 12, 2024
1 parent c564bbf commit 723c398
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 1 deletion.
3 changes: 3 additions & 0 deletions lib/datadog/ci/ext/transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ module Transport

DD_API_GIT_UPLOAD_PACKFILE_PATH = "/api/v2/git/repository/packfile"

DD_API_SKIPPABLE_TESTS_PATH = "/api/v2/ci/tests/skippable"
DD_API_SKIPPABLE_TESTS_TYPE = "test_params"

CONTENT_TYPE_MESSAGEPACK = "application/msgpack"
CONTENT_TYPE_JSON = "application/json"
CONTENT_TYPE_MULTIPART_FORM_DATA = "multipart/form-data"
Expand Down
113 changes: 113 additions & 0 deletions lib/datadog/ci/itr/skippable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# frozen_string_literal: true

require "json"

require_relative "../ext/transport"
require_relative "../ext/test"

module Datadog
module CI
module ITR
class Skippable
class Test
attr_reader :name, :suite

def initialize(name:, suite:)
@name = name
@suite = suite
end

def ==(other)
name == other.name && suite == other.suite
end
end

class Response
def initialize(http_response)
@http_response = http_response
@json = nil
end

def ok?
resp = @http_response
!resp.nil? && resp.ok?
end

def correlation_id
payload.dig("meta", "correlation_id")
end

def tests
payload.fetch("data", [])
.filter_map do |test_data|
next unless test_data["type"] == Ext::Test::ITR_TEST_SKIPPING_MODE

attrs = test_data["attributes"] || {}
Test.new(name: attrs["name"], suite: attrs["suite"])
end
end

private

def payload
cached = @json
return cached unless cached.nil?

resp = @http_response
return @json = {} if resp.nil? || !ok?

begin
@json = JSON.parse(resp.payload)
rescue JSON::ParserError => e
Datadog.logger.error("Failed to parse skippable tests response payload: #{e}. Payload was: #{resp.payload}")
@json = {}
end
end
end

def initialize(api: nil, dd_env: nil)
@api = api
@dd_env = dd_env
end

def fetch_skippable_tests(test_session)
api = @api
return Response.new(nil) unless api

request_payload = payload(test_session)
Datadog.logger.debug("Fetching skippable tests with request: #{request_payload}")

http_response = api.api_request(
path: Ext::Transport::DD_API_SKIPPABLE_TESTS_PATH,
payload: request_payload
)

Response.new(http_response)
end

private

def payload(test_session)
{
"data" => {
"type" => Ext::Transport::DD_API_SKIPPABLE_TESTS_TYPE,
"attributes" => {
"test_level" => Ext::Test::ITR_TEST_SKIPPING_MODE,
"service" => test_session.service,
"env" => @dd_env,
"repository_url" => test_session.git_repository_url,
"sha" => test_session.git_commit_sha,
"configurations" => {
"os.platform" => test_session.os_platform,
"os.architecture" => test_session.os_architecture,
"runtime.name" => test_session.runtime_name,
"runtime.version" => test_session.runtime_version
}
}
}
}.to_json
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/datadog/ci/transport/remote_settings_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def payload
return cached unless cached.nil?

resp = @http_response
return @json = default_payload if resp.nil? || !resp.ok?
return @json = default_payload if resp.nil? || !ok?

begin
@json = JSON.parse(resp.payload).dig(*Ext::Transport::DD_API_SETTINGS_RESPONSE_DIG_KEYS) ||
Expand Down
4 changes: 4 additions & 0 deletions sig/datadog/ci/ext/transport.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ module Datadog

DD_API_GIT_UPLOAD_PACKFILE_PATH: "/api/v2/git/repository/packfile"

DD_API_SKIPPABLE_TESTS_PATH: "/api/v2/ci/tests/skippable"

DD_API_SKIPPABLE_TESTS_TYPE: "test_params"

CONTENT_TYPE_MESSAGEPACK: "application/msgpack"

CONTENT_TYPE_JSON: "application/json"
Expand Down
47 changes: 47 additions & 0 deletions sig/datadog/ci/itr/skippable.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module Datadog
module CI
module ITR
class Skippable
@api: Datadog::CI::Transport::Api::Base?
@dd_env: String?

class Test
@name: String?

@suite: String?

attr_reader name: String?

attr_reader suite: String?

def initialize: (name: String?, suite: String?) -> void
end

class Response
@http_response: Datadog::Core::Transport::HTTP::Adapters::Net::Response?
@json: Hash[String, untyped]?

def initialize: (Datadog::Core::Transport::HTTP::Adapters::Net::Response? http_response) -> void

def ok?: () -> bool

def correlation_id: () -> String?

def tests: () -> Array[Test]

private

def payload: () -> Hash[String, untyped]
end

def initialize: (?api: Datadog::CI::Transport::Api::Base?, ?dd_env: String?) -> void

def fetch_skippable_tests: (Datadog::CI::TestSession test_session) -> Response

private

def payload: (Datadog::CI::TestSession test_session) -> String
end
end
end
end
2 changes: 2 additions & 0 deletions spec/datadog/ci/configuration/settings_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# Dummy Integration
class FakeIntegration
include Datadog::CI::Contrib::Integration
Expand Down
180 changes: 180 additions & 0 deletions spec/datadog/ci/itr/skippable_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# frozen_string_literal: true

require_relative "../../../../lib/datadog/ci/itr/skippable"

RSpec.describe Datadog::CI::ITR::Skippable do
let(:api) { spy("api") }
let(:dd_env) { "ci" }

subject(:client) { described_class.new(api: api, dd_env: dd_env) }

describe "#fetch_skippable_tests" do
let(:service) { "service" }
let(:tracer_span) do
Datadog::Tracing::SpanOperation.new("session", service: service).tap do |span|
span.set_tags({
"git.repository_url" => "repository_url",
"git.branch" => "branch",
"git.commit.sha" => "commit_sha",
"os.platform" => "platform",
"os.architecture" => "arch",
"runtime.name" => "runtime_name",
"runtime.version" => "runtime_version"
})
end
end
let(:test_session) { Datadog::CI::TestSession.new(tracer_span) }

let(:path) { Datadog::CI::Ext::Transport::DD_API_SKIPPABLE_TESTS_PATH }

it "requests the skippable tests" do
client.fetch_skippable_tests(test_session)

expect(api).to have_received(:api_request) do |args|
expect(args[:path]).to eq(path)

data = JSON.parse(args[:payload])["data"]

expect(data["type"]).to eq(Datadog::CI::Ext::Transport::DD_API_SKIPPABLE_TESTS_TYPE)

attributes = data["attributes"]
expect(attributes["service"]).to eq(service)
expect(attributes["env"]).to eq(dd_env)
expect(attributes["test_level"]).to eq("test")
expect(attributes["repository_url"]).to eq("repository_url")
expect(attributes["sha"]).to eq("commit_sha")

configurations = attributes["configurations"]
expect(configurations["os.platform"]).to eq("platform")
expect(configurations["os.architecture"]).to eq("arch")
expect(configurations["runtime.name"]).to eq("runtime_name")
expect(configurations["runtime.version"]).to eq("runtime_version")
end
end

context "parsing response" do
subject(:response) { client.fetch_skippable_tests(test_session) }

context "when api is present" do
before do
allow(api).to receive(:api_request).and_return(http_response)
end

context "when response is OK" do
let(:http_response) do
double(
"http_response",
ok?: true,
payload: {
"meta" => {
"correlation_id" => "correlation_id_123"
},
"data" => [
{
"id" => "123",
"type" => Datadog::CI::Ext::Test::ITR_TEST_SKIPPING_MODE,
"attributes" => {
"suite" => "test_suite_name",
"name" => "test_name",
"parameters" => "string",
"configurations" => {
"os.platform" => "linux",
"os.version" => "bionic",
"os.architecture" => "amd64",
"runtime.vendor" => "string",
"runtime.architecture" => "amd64"
}
}
}
]
}.to_json
)
end

it "parses the response" do
expect(response.ok?).to be true
expect(response.correlation_id).to eq("correlation_id_123")
expect(response.tests.first).to eq(
Datadog::CI::ITR::Skippable::Test.new(name: "test_name", suite: "test_suite_name")
)
end
end

context "when response is not OK" do
let(:http_response) do
double(
"http_response",
ok?: false,
payload: ""
)
end

it "parses the response" do
expect(response.ok?).to be false
expect(response.correlation_id).to be_nil
expect(response.tests).to be_empty
end
end

context "when response is OK but JSON is malformed" do
let(:http_response) do
double(
"http_response",
ok?: true,
payload: "not json"
)
end

before do
expect(Datadog.logger).to receive(:error).with(/Failed to parse skippable tests response payload/)
end

it "parses the response" do
expect(response.ok?).to be true
expect(response.correlation_id).to be_nil
expect(response.tests).to be_empty
end
end

context "when response is OK but JSON has different format" do
let(:http_response) do
double(
"http_response",
ok?: true,
payload: {
"attributes" => {
"suite" => "test_suite_name",
"name" => "test_name",
"parameters" => "string",
"configurations" => {
"os.platform" => "linux",
"os.version" => "bionic",
"os.architecture" => "amd64",
"runtime.vendor" => "string",
"runtime.architecture" => "amd64"
}
}
}.to_json
)
end

it "parses the response" do
expect(response.ok?).to be true
expect(response.correlation_id).to be_nil
expect(response.tests).to be_empty
end
end
end

context "when there is no api" do
let(:api) { nil }

it "returns an empty response" do
expect(response.ok?).to be false
expect(response.correlation_id).to be_nil
expect(response.tests).to be_empty
end
end
end
end
end
2 changes: 2 additions & 0 deletions spec/datadog/ci/transport/remote_settings_api_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require_relative "../../../../lib/datadog/ci/transport/remote_settings_api"

RSpec.describe Datadog::CI::Transport::RemoteSettingsApi do
Expand Down

0 comments on commit 723c398

Please sign in to comment.