aboutsummaryrefslogtreecommitdiff
path: root/kamon-core-tests/src/test/scala/kamon/context/HttpPropagationSpec.scala
blob: fcddfe24166d4618d86250c8836e97e0a1f5a60d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package kamon.context

import com.typesafe.config.ConfigFactory
import kamon.Kamon
import kamon.context.HttpPropagation.{HeaderReader, HeaderWriter}
import kamon.context.Propagation.{EntryReader, EntryWriter}
import org.scalatest.{Matchers, OptionValues, WordSpec}

import scala.collection.mutable

class HttpPropagationSpec extends WordSpec with Matchers with OptionValues {

  "The HTTP Context Propagation" when {
    "reading from incoming requests" should {
      "return an empty context if there are no tags nor keys" in {
        val context = httpPropagation.read(headerReaderFromMap(Map.empty))
        context.isEmpty() shouldBe true
      }

      "read tags from an HTTP message when they are available" in {
        val headers = Map(
          "x-content-tags" -> "hello=world;correlation=1234",
          "x-mapped-tag" -> "value"
        )
        val context = httpPropagation.read(headerReaderFromMap(headers))
        context.tags should contain only(
          "hello" -> "world",
          "correlation" -> "1234",
          "mappedTag" -> "value"
        )
      }

      "handle errors when reading HTTP headers" in {
        val headers = Map("fail" -> "")
        val context = httpPropagation.read(headerReaderFromMap(headers))
        context.tags shouldBe empty
        context.entries shouldBe empty
      }

      "read context with entries and tags" in {
        val headers = Map(
          "x-content-tags" -> "hello=world;correlation=1234",
          "string-header" -> "hey",
          "integer-header" -> "123"
        )

        val context = httpPropagation.read(headerReaderFromMap(headers))
        context.get(HttpPropagationSpec.StringKey) shouldBe "hey"
        context.get(HttpPropagationSpec.IntegerKey) shouldBe 123
        context.get(HttpPropagationSpec.OptionalKey) shouldBe empty
        context.getTag("hello").value shouldBe "world"
        context.getTag("correlation").value shouldBe "1234"
        context.getTag("unknown") shouldBe empty
      }
    }


    "writing to outgoing requests" should {
      "not write anything if the context is empty" in {
        val headers = mutable.Map.empty[String, String]
        httpPropagation.write(Context.Empty, headerWriterFromMap(headers))
        headers shouldBe empty
      }

      "write context tags when available" in {
        val headers = mutable.Map.empty[String, String]
        val context = Context.of(Map(
          "hello" -> "world",
          "mappedTag" -> "value"
        ))

        httpPropagation.write(context, headerWriterFromMap(headers))
        headers should contain only(
          "x-content-tags" -> "hello=world;",
          "x-mapped-tag" -> "value"
        )
      }

      "write context entries when available" in {
        val headers = mutable.Map.empty[String, String]
        val context = Context.of(
          HttpPropagationSpec.StringKey, "out-we-go",
          HttpPropagationSpec.IntegerKey, 42
        )

        httpPropagation.write(context, headerWriterFromMap(headers))
        headers should contain only(
          "string-header" -> "out-we-go"
          )
      }
    }
  }


  val httpPropagation = HttpPropagation.from(
    ConfigFactory.parseString(
      """
        |tags {
        |  header-name = "x-content-tags"
        |
        |  mappings {
        |    mappedTag = "x-mapped-tag"
        |  }
        |}
        |
        |entries.incoming.string = "kamon.context.HttpPropagationSpec$StringEntryCodec"
        |entries.incoming.integer = "kamon.context.HttpPropagationSpec$IntegerEntryCodec"
        |entries.outgoing.string = "kamon.context.HttpPropagationSpec$StringEntryCodec"
        |
      """.stripMargin
    ).withFallback(ConfigFactory.load().getConfig("kamon.propagation")), Kamon)


  def headerReaderFromMap(map: Map[String, String]): HttpPropagation.HeaderReader = new HttpPropagation.HeaderReader {
    override def read(header: String): Option[String] = {
      if(map.get("fail").nonEmpty)
        sys.error("failing on purpose")

      map.get(header)
    }

    override def readAll(): Map[String, String] = map
  }

  def headerWriterFromMap(map: mutable.Map[String, String]): HttpPropagation.HeaderWriter = new HttpPropagation.HeaderWriter {
    override def write(header: String, value: String): Unit = map.put(header, value)
  }
}

object HttpPropagationSpec {

  val StringKey = Context.key[String]("string", null)
  val IntegerKey = Context.key[Int]("integer", 0)
  val OptionalKey = Context.key[Option[String]]("optional", None)


  class StringEntryCodec extends EntryReader[HeaderReader] with EntryWriter[HeaderWriter] {
    private val HeaderName = "string-header"

    override def read(reader: HttpPropagation.HeaderReader, context: Context): Context = {
      reader.read(HeaderName)
        .map(v => context.withKey(StringKey, v))
        .getOrElse(context)
    }

    override def write(context: Context, writer: HttpPropagation.HeaderWriter): Unit = {
      Option(context.get(StringKey)).foreach(v => writer.write(HeaderName, v))
    }
  }

  class IntegerEntryCodec extends EntryReader[HeaderReader] {
    override def read(reader: HttpPropagation.HeaderReader, context: Context): Context = {
      reader.read("integer-header")
        .map(v => context.withKey(IntegerKey, v.toInt))
        .getOrElse(context)

    }
  }
}