Skip to content
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

[CIVIS-2934] skippable tests api client #158

Merged
merged 2 commits into from
Apr 12, 2024
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
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
romainkomorndatadog marked this conversation as resolved.
Show resolved Hide resolved
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" => {
romainkomorndatadog marked this conversation as resolved.
Show resolved Hide resolved
"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
4 changes: 4 additions & 0 deletions lib/datadog/ci/test_visibility/recorder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ def configure_library(test_session)

Datadog.logger.debug { "Requesting library configuration again..." }
remote_configuration = @remote_settings_api.fetch_library_settings(test_session)

if remote_configuration.require_git?
Datadog.logger.debug { "git metadata upload did not complete in time when configuring library" }
end
end
@itr.configure(remote_configuration.payload, test_session)
end
Expand Down
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
Loading