client_test.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
  2. package opencode_test
  3. import (
  4. "context"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "reflect"
  9. "testing"
  10. "time"
  11. "github.com/sst/opencode-sdk-go"
  12. "github.com/sst/opencode-sdk-go/internal"
  13. "github.com/sst/opencode-sdk-go/option"
  14. )
  15. type closureTransport struct {
  16. fn func(req *http.Request) (*http.Response, error)
  17. }
  18. func (t *closureTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  19. return t.fn(req)
  20. }
  21. func TestUserAgentHeader(t *testing.T) {
  22. var userAgent string
  23. client := opencode.NewClient(
  24. option.WithHTTPClient(&http.Client{
  25. Transport: &closureTransport{
  26. fn: func(req *http.Request) (*http.Response, error) {
  27. userAgent = req.Header.Get("User-Agent")
  28. return &http.Response{
  29. StatusCode: http.StatusOK,
  30. }, nil
  31. },
  32. },
  33. }),
  34. )
  35. client.Session.List(context.Background(), opencode.SessionListParams{})
  36. if userAgent != fmt.Sprintf("Opencode/Go %s", internal.PackageVersion) {
  37. t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent)
  38. }
  39. }
  40. func TestRetryAfter(t *testing.T) {
  41. retryCountHeaders := make([]string, 0)
  42. client := opencode.NewClient(
  43. option.WithHTTPClient(&http.Client{
  44. Transport: &closureTransport{
  45. fn: func(req *http.Request) (*http.Response, error) {
  46. retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
  47. return &http.Response{
  48. StatusCode: http.StatusTooManyRequests,
  49. Header: http.Header{
  50. http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
  51. },
  52. }, nil
  53. },
  54. },
  55. }),
  56. )
  57. _, err := client.Session.List(context.Background(), opencode.SessionListParams{})
  58. if err == nil {
  59. t.Error("Expected there to be a cancel error")
  60. }
  61. attempts := len(retryCountHeaders)
  62. if attempts != 3 {
  63. t.Errorf("Expected %d attempts, got %d", 3, attempts)
  64. }
  65. expectedRetryCountHeaders := []string{"0", "1", "2"}
  66. if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
  67. t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
  68. }
  69. }
  70. func TestDeleteRetryCountHeader(t *testing.T) {
  71. retryCountHeaders := make([]string, 0)
  72. client := opencode.NewClient(
  73. option.WithHTTPClient(&http.Client{
  74. Transport: &closureTransport{
  75. fn: func(req *http.Request) (*http.Response, error) {
  76. retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
  77. return &http.Response{
  78. StatusCode: http.StatusTooManyRequests,
  79. Header: http.Header{
  80. http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
  81. },
  82. }, nil
  83. },
  84. },
  85. }),
  86. option.WithHeaderDel("X-Stainless-Retry-Count"),
  87. )
  88. _, err := client.Session.List(context.Background(), opencode.SessionListParams{})
  89. if err == nil {
  90. t.Error("Expected there to be a cancel error")
  91. }
  92. expectedRetryCountHeaders := []string{"", "", ""}
  93. if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
  94. t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
  95. }
  96. }
  97. func TestOverwriteRetryCountHeader(t *testing.T) {
  98. retryCountHeaders := make([]string, 0)
  99. client := opencode.NewClient(
  100. option.WithHTTPClient(&http.Client{
  101. Transport: &closureTransport{
  102. fn: func(req *http.Request) (*http.Response, error) {
  103. retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
  104. return &http.Response{
  105. StatusCode: http.StatusTooManyRequests,
  106. Header: http.Header{
  107. http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
  108. },
  109. }, nil
  110. },
  111. },
  112. }),
  113. option.WithHeader("X-Stainless-Retry-Count", "42"),
  114. )
  115. _, err := client.Session.List(context.Background(), opencode.SessionListParams{})
  116. if err == nil {
  117. t.Error("Expected there to be a cancel error")
  118. }
  119. expectedRetryCountHeaders := []string{"42", "42", "42"}
  120. if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
  121. t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
  122. }
  123. }
  124. func TestRetryAfterMs(t *testing.T) {
  125. attempts := 0
  126. client := opencode.NewClient(
  127. option.WithHTTPClient(&http.Client{
  128. Transport: &closureTransport{
  129. fn: func(req *http.Request) (*http.Response, error) {
  130. attempts++
  131. return &http.Response{
  132. StatusCode: http.StatusTooManyRequests,
  133. Header: http.Header{
  134. http.CanonicalHeaderKey("Retry-After-Ms"): []string{"100"},
  135. },
  136. }, nil
  137. },
  138. },
  139. }),
  140. )
  141. _, err := client.Session.List(context.Background(), opencode.SessionListParams{})
  142. if err == nil {
  143. t.Error("Expected there to be a cancel error")
  144. }
  145. if want := 3; attempts != want {
  146. t.Errorf("Expected %d attempts, got %d", want, attempts)
  147. }
  148. }
  149. func TestContextCancel(t *testing.T) {
  150. client := opencode.NewClient(
  151. option.WithHTTPClient(&http.Client{
  152. Transport: &closureTransport{
  153. fn: func(req *http.Request) (*http.Response, error) {
  154. <-req.Context().Done()
  155. return nil, req.Context().Err()
  156. },
  157. },
  158. }),
  159. )
  160. cancelCtx, cancel := context.WithCancel(context.Background())
  161. cancel()
  162. _, err := client.Session.List(cancelCtx, opencode.SessionListParams{})
  163. if err == nil {
  164. t.Error("Expected there to be a cancel error")
  165. }
  166. }
  167. func TestContextCancelDelay(t *testing.T) {
  168. client := opencode.NewClient(
  169. option.WithHTTPClient(&http.Client{
  170. Transport: &closureTransport{
  171. fn: func(req *http.Request) (*http.Response, error) {
  172. <-req.Context().Done()
  173. return nil, req.Context().Err()
  174. },
  175. },
  176. }),
  177. )
  178. cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
  179. defer cancel()
  180. _, err := client.Session.List(cancelCtx, opencode.SessionListParams{})
  181. if err == nil {
  182. t.Error("expected there to be a cancel error")
  183. }
  184. }
  185. func TestContextDeadline(t *testing.T) {
  186. testTimeout := time.After(3 * time.Second)
  187. testDone := make(chan struct{})
  188. deadline := time.Now().Add(100 * time.Millisecond)
  189. deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
  190. defer cancel()
  191. go func() {
  192. client := opencode.NewClient(
  193. option.WithHTTPClient(&http.Client{
  194. Transport: &closureTransport{
  195. fn: func(req *http.Request) (*http.Response, error) {
  196. <-req.Context().Done()
  197. return nil, req.Context().Err()
  198. },
  199. },
  200. }),
  201. )
  202. _, err := client.Session.List(deadlineCtx, opencode.SessionListParams{})
  203. if err == nil {
  204. t.Error("expected there to be a deadline error")
  205. }
  206. close(testDone)
  207. }()
  208. select {
  209. case <-testTimeout:
  210. t.Fatal("client didn't finish in time")
  211. case <-testDone:
  212. if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
  213. t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
  214. }
  215. }
  216. }
  217. func TestContextDeadlineStreaming(t *testing.T) {
  218. testTimeout := time.After(3 * time.Second)
  219. testDone := make(chan struct{})
  220. deadline := time.Now().Add(100 * time.Millisecond)
  221. deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
  222. defer cancel()
  223. go func() {
  224. client := opencode.NewClient(
  225. option.WithHTTPClient(&http.Client{
  226. Transport: &closureTransport{
  227. fn: func(req *http.Request) (*http.Response, error) {
  228. return &http.Response{
  229. StatusCode: 200,
  230. Status: "200 OK",
  231. Body: io.NopCloser(
  232. io.Reader(readerFunc(func([]byte) (int, error) {
  233. <-req.Context().Done()
  234. return 0, req.Context().Err()
  235. })),
  236. ),
  237. }, nil
  238. },
  239. },
  240. }),
  241. )
  242. stream := client.Event.ListStreaming(deadlineCtx, opencode.EventListParams{})
  243. for stream.Next() {
  244. _ = stream.Current()
  245. }
  246. if stream.Err() == nil {
  247. t.Error("expected there to be a deadline error")
  248. }
  249. close(testDone)
  250. }()
  251. select {
  252. case <-testTimeout:
  253. t.Fatal("client didn't finish in time")
  254. case <-testDone:
  255. if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
  256. t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
  257. }
  258. }
  259. }
  260. func TestContextDeadlineStreamingWithRequestTimeout(t *testing.T) {
  261. testTimeout := time.After(3 * time.Second)
  262. testDone := make(chan struct{})
  263. deadline := time.Now().Add(100 * time.Millisecond)
  264. go func() {
  265. client := opencode.NewClient(
  266. option.WithHTTPClient(&http.Client{
  267. Transport: &closureTransport{
  268. fn: func(req *http.Request) (*http.Response, error) {
  269. return &http.Response{
  270. StatusCode: 200,
  271. Status: "200 OK",
  272. Body: io.NopCloser(
  273. io.Reader(readerFunc(func([]byte) (int, error) {
  274. <-req.Context().Done()
  275. return 0, req.Context().Err()
  276. })),
  277. ),
  278. }, nil
  279. },
  280. },
  281. }),
  282. )
  283. stream := client.Event.ListStreaming(
  284. context.Background(),
  285. opencode.EventListParams{},
  286. option.WithRequestTimeout((100 * time.Millisecond)),
  287. )
  288. for stream.Next() {
  289. _ = stream.Current()
  290. }
  291. if stream.Err() == nil {
  292. t.Error("expected there to be a deadline error")
  293. }
  294. close(testDone)
  295. }()
  296. select {
  297. case <-testTimeout:
  298. t.Fatal("client didn't finish in time")
  299. case <-testDone:
  300. if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
  301. t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
  302. }
  303. }
  304. }
  305. type readerFunc func([]byte) (int, error)
  306. func (f readerFunc) Read(p []byte) (int, error) { return f(p) }
  307. func (f readerFunc) Close() error { return nil }