diff --git a/README.md b/README.md
index 99d6bf0d5dd..26bddc4add3 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,8 @@ gather all traces in your [Datadog](https://app.datadoghq.com) account.
* [Introduction to the Datadog APM](https://www.datadoghq.com/apm/). Learn what you can do with the Next-Gen APM and how to get started.
* [Install the Datadog Java agent](dd-java-agent). Instructions for supported technologies, web-servers and frameworks.
* [Browse examples](dd-trace-examples). See how to instrument legacy projects based on the most used tehcnologies.
-* [DD Trace API](dd-trace). We choose to embrace the Opentracting initiative. So feel free to use the Trace Java API to customize your instrumentation.
+* [Instrument with OpenTracing](https://github.com/opentracing/opentracing-java). Datadog embraces the OpenTracing initiative. So feel free to use the Trace Java API to customize your instrumentation.
+* [DD Trace](dd-trace). This Java implementation of the opentracing api is used to report traces to Datadog.
### Help or questions?
diff --git a/dd-trace/pom.xml b/dd-trace/pom.xml
index 723553ce334..4c67f100c94 100644
--- a/dd-trace/pom.xml
+++ b/dd-trace/pom.xml
@@ -88,6 +88,24 @@
2.7.22
test
+
+ org.spockframework
+ spock-core
+ 1.0-groovy-2.4
+ test
+
+
+ org.codehaus.groovy
+ groovy-all
+ 2.4.4
+ test
+
+
+ io.ratpack
+ ratpack-groovy-test
+ 1.4.6
+ test
+
diff --git a/dd-trace/src/test/groovy/com/datadog/trace/SpanFactory.groovy b/dd-trace/src/test/groovy/com/datadog/trace/SpanFactory.groovy
new file mode 100644
index 00000000000..67b7a1c8b7e
--- /dev/null
+++ b/dd-trace/src/test/groovy/com/datadog/trace/SpanFactory.groovy
@@ -0,0 +1,23 @@
+package com.datadog.trace
+
+import com.datadoghq.trace.DDSpan
+import com.datadoghq.trace.DDSpanContext
+
+class SpanFactory {
+ static def newSpanOf(long timestampMicro) {
+ def context = new DDSpanContext(
+ 1L,
+ 1L,
+ 0L,
+ "fakeService",
+ "fakeOperation",
+ "fakeResource",
+ Collections.emptyMap(),
+ false,
+ "fakeType",
+ Collections.emptyMap(),
+ null,
+ null);
+ return new DDSpan(timestampMicro, context)
+ }
+}
diff --git a/dd-trace/src/test/groovy/com/datadoghq/trace/writer/DDApiTest.groovy b/dd-trace/src/test/groovy/com/datadoghq/trace/writer/DDApiTest.groovy
new file mode 100644
index 00000000000..2af05eb56c7
--- /dev/null
+++ b/dd-trace/src/test/groovy/com/datadoghq/trace/writer/DDApiTest.groovy
@@ -0,0 +1,117 @@
+package com.datadoghq.trace.writer
+
+import com.datadog.trace.SpanFactory
+import com.datadoghq.trace.DDSpan
+import com.fasterxml.jackson.databind.ObjectMapper
+import ratpack.http.MediaType
+import spock.lang.AutoCleanup
+import spock.lang.Specification
+
+import java.util.concurrent.atomic.AtomicReference
+
+import static ratpack.groovy.test.embed.GroovyEmbeddedApp.ratpack
+import static ratpack.http.MediaType.APPLICATION_JSON
+
+class DDApiTest extends Specification {
+ static def mapper = new ObjectMapper()
+
+ def "sending an empty list of traces returns no errors"() {
+ setup:
+ def agent = ratpack {
+ handlers {
+ put("v0.3/traces") {
+ response.status(200).send()
+ }
+ }
+ }
+ def client = new DDApi("localhost", agent.address.port)
+
+ expect:
+ client.sendTraces([])
+
+ cleanup:
+ agent.close()
+ }
+
+ def "non-200 response results in false returned"() {
+ setup:
+ def agent = ratpack {
+ handlers {
+ put("v0.3/traces") {
+ response.status(404).send()
+ }
+ }
+ }
+ def client = new DDApi("localhost", agent.address.port)
+
+ expect:
+ !client.sendTraces([])
+
+ cleanup:
+ agent.close()
+ }
+
+ def "content is sent as JSON"() {
+ setup:
+ def requestContentType = new AtomicReference()
+ def requestBody = new AtomicReference()
+ def agent = ratpack {
+ handlers {
+ put("v0.3/traces") {
+ requestContentType.set(request.contentType)
+ request.body.then {
+ requestBody.set(it.text)
+ response.send()
+ }
+ }
+ }
+ }
+ def client = new DDApi("localhost", agent.address.port)
+
+ expect:
+ client.sendTraces(traces)
+ requestContentType.get().type == APPLICATION_JSON
+ areEqual(requestBody.get(), expectedRequestBody)
+
+ cleanup:
+ agent.close()
+
+ where:
+ traces | expectedRequestBody
+ [] | '[]'
+ [SpanFactory.newSpanOf(1L)] | '''[{
+ "duration":0,
+ "error":0,
+ "meta":{"thread-name":"main","thread-id":"1"},
+ "name":"fakeOperation",
+ "parent_id":0,
+ "resource":"fakeResource"
+ "service":"fakeService",
+ "span_id":1,
+ "start":1000,
+ "trace_id":1,
+ "type":"fakeType",
+ }]'''
+ [SpanFactory.newSpanOf(100L)] | '''[{
+ "duration":0,
+ "error":0,
+ "meta":{"thread-name":"main","thread-id":"1"},
+ "name":"fakeOperation",
+ "parent_id":0,
+ "resource":"fakeResource"
+ "service":"fakeService",
+ "span_id":1,
+ "start":100000,
+ "trace_id":1,
+ "type":"fakeType",
+ }]'''
+ }
+
+
+ static void areEqual(String json1, String json2) {
+ def tree1 = mapper.readTree json1
+ def tree2 = mapper.readTree json2
+
+ assert tree1.equals(tree2)
+ }
+}