trace-attributes.test.ts 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { describe, expect, test } from "bun:test"
  2. import { paramToAttributeKey, requestAttributes } from "../../src/server/routes/instance/trace"
  3. function fakeContext(method: string, url: string, params: Record<string, string>) {
  4. return {
  5. req: {
  6. method,
  7. url,
  8. param: () => params,
  9. },
  10. }
  11. }
  12. describe("paramToAttributeKey", () => {
  13. test("converts fooID to foo.id", () => {
  14. expect(paramToAttributeKey("sessionID")).toBe("session.id")
  15. expect(paramToAttributeKey("messageID")).toBe("message.id")
  16. expect(paramToAttributeKey("partID")).toBe("part.id")
  17. expect(paramToAttributeKey("projectID")).toBe("project.id")
  18. expect(paramToAttributeKey("providerID")).toBe("provider.id")
  19. expect(paramToAttributeKey("ptyID")).toBe("pty.id")
  20. expect(paramToAttributeKey("permissionID")).toBe("permission.id")
  21. expect(paramToAttributeKey("requestID")).toBe("request.id")
  22. expect(paramToAttributeKey("workspaceID")).toBe("workspace.id")
  23. })
  24. test("namespaces non-ID params under opencode.", () => {
  25. expect(paramToAttributeKey("name")).toBe("opencode.name")
  26. expect(paramToAttributeKey("slug")).toBe("opencode.slug")
  27. })
  28. })
  29. describe("requestAttributes", () => {
  30. test("includes http method and path", () => {
  31. const attrs = requestAttributes(fakeContext("GET", "http://localhost/session", {}))
  32. expect(attrs["http.method"]).toBe("GET")
  33. expect(attrs["http.path"]).toBe("/session")
  34. })
  35. test("strips query string from path", () => {
  36. const attrs = requestAttributes(fakeContext("GET", "http://localhost/file/search?query=foo&limit=10", {}))
  37. expect(attrs["http.path"]).toBe("/file/search")
  38. })
  39. test("emits OTel-style <domain>.id for ID-shaped route params", () => {
  40. const attrs = requestAttributes(
  41. fakeContext("GET", "http://localhost/session/ses_abc/message/msg_def/part/prt_ghi", {
  42. sessionID: "ses_abc",
  43. messageID: "msg_def",
  44. partID: "prt_ghi",
  45. }),
  46. )
  47. expect(attrs["session.id"]).toBe("ses_abc")
  48. expect(attrs["message.id"]).toBe("msg_def")
  49. expect(attrs["part.id"]).toBe("prt_ghi")
  50. // No camelCase leftovers:
  51. expect(attrs["opencode.sessionID"]).toBeUndefined()
  52. expect(attrs["opencode.messageID"]).toBeUndefined()
  53. expect(attrs["opencode.partID"]).toBeUndefined()
  54. })
  55. test("produces no param attributes when no params are matched", () => {
  56. const attrs = requestAttributes(fakeContext("POST", "http://localhost/config", {}))
  57. expect(Object.keys(attrs).filter((k) => k !== "http.method" && k !== "http.path")).toEqual([])
  58. })
  59. test("namespaces non-ID params under opencode. (e.g. mcp :name)", () => {
  60. const attrs = requestAttributes(
  61. fakeContext("POST", "http://localhost/mcp/exa/connect", {
  62. name: "exa",
  63. }),
  64. )
  65. expect(attrs["opencode.name"]).toBe("exa")
  66. expect(attrs["name"]).toBeUndefined()
  67. })
  68. })