client_test.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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.Event.ListStreaming(context.Background())
  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. stream := client.Event.ListStreaming(context.Background())
  58. for stream.Next() {
  59. // ...
  60. }
  61. err := stream.Err()
  62. if err == nil {
  63. t.Error("Expected there to be a cancel error")
  64. }
  65. attempts := len(retryCountHeaders)
  66. if attempts != 3 {
  67. t.Errorf("Expected %d attempts, got %d", 3, attempts)
  68. }
  69. expectedRetryCountHeaders := []string{"0", "1", "2"}
  70. if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
  71. t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
  72. }
  73. }
  74. func TestDeleteRetryCountHeader(t *testing.T) {
  75. retryCountHeaders := make([]string, 0)
  76. client := opencode.NewClient(
  77. option.WithHTTPClient(&http.Client{
  78. Transport: &closureTransport{
  79. fn: func(req *http.Request) (*http.Response, error) {
  80. retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
  81. return &http.Response{
  82. StatusCode: http.StatusTooManyRequests,
  83. Header: http.Header{
  84. http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
  85. },
  86. }, nil
  87. },
  88. },
  89. }),
  90. option.WithHeaderDel("X-Stainless-Retry-Count"),
  91. )
  92. stream := client.Event.ListStreaming(context.Background())
  93. for stream.Next() {
  94. // ...
  95. }
  96. err := stream.Err()
  97. if err == nil {
  98. t.Error("Expected there to be a cancel error")
  99. }
  100. expectedRetryCountHeaders := []string{"", "", ""}
  101. if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
  102. t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
  103. }
  104. }
  105. func TestOverwriteRetryCountHeader(t *testing.T) {
  106. retryCountHeaders := make([]string, 0)
  107. client := opencode.NewClient(
  108. option.WithHTTPClient(&http.Client{
  109. Transport: &closureTransport{
  110. fn: func(req *http.Request) (*http.Response, error) {
  111. retryCountHeaders = append(retryCountHeaders, req.Header.Get("X-Stainless-Retry-Count"))
  112. return &http.Response{
  113. StatusCode: http.StatusTooManyRequests,
  114. Header: http.Header{
  115. http.CanonicalHeaderKey("Retry-After"): []string{"0.1"},
  116. },
  117. }, nil
  118. },
  119. },
  120. }),
  121. option.WithHeader("X-Stainless-Retry-Count", "42"),
  122. )
  123. stream := client.Event.ListStreaming(context.Background())
  124. for stream.Next() {
  125. // ...
  126. }
  127. err := stream.Err()
  128. if err == nil {
  129. t.Error("Expected there to be a cancel error")
  130. }
  131. expectedRetryCountHeaders := []string{"42", "42", "42"}
  132. if !reflect.DeepEqual(retryCountHeaders, expectedRetryCountHeaders) {
  133. t.Errorf("Expected %v retry count headers, got %v", expectedRetryCountHeaders, retryCountHeaders)
  134. }
  135. }
  136. func TestRetryAfterMs(t *testing.T) {
  137. attempts := 0
  138. client := opencode.NewClient(
  139. option.WithHTTPClient(&http.Client{
  140. Transport: &closureTransport{
  141. fn: func(req *http.Request) (*http.Response, error) {
  142. attempts++
  143. return &http.Response{
  144. StatusCode: http.StatusTooManyRequests,
  145. Header: http.Header{
  146. http.CanonicalHeaderKey("Retry-After-Ms"): []string{"100"},
  147. },
  148. }, nil
  149. },
  150. },
  151. }),
  152. )
  153. stream := client.Event.ListStreaming(context.Background())
  154. for stream.Next() {
  155. // ...
  156. }
  157. err := stream.Err()
  158. if err == nil {
  159. t.Error("Expected there to be a cancel error")
  160. }
  161. if want := 3; attempts != want {
  162. t.Errorf("Expected %d attempts, got %d", want, attempts)
  163. }
  164. }
  165. func TestContextCancel(t *testing.T) {
  166. client := opencode.NewClient(
  167. option.WithHTTPClient(&http.Client{
  168. Transport: &closureTransport{
  169. fn: func(req *http.Request) (*http.Response, error) {
  170. <-req.Context().Done()
  171. return nil, req.Context().Err()
  172. },
  173. },
  174. }),
  175. )
  176. cancelCtx, cancel := context.WithCancel(context.Background())
  177. cancel()
  178. stream := client.Event.ListStreaming(cancelCtx)
  179. for stream.Next() {
  180. // ...
  181. }
  182. err := stream.Err()
  183. if err == nil {
  184. t.Error("Expected there to be a cancel error")
  185. }
  186. }
  187. func TestContextCancelDelay(t *testing.T) {
  188. client := opencode.NewClient(
  189. option.WithHTTPClient(&http.Client{
  190. Transport: &closureTransport{
  191. fn: func(req *http.Request) (*http.Response, error) {
  192. <-req.Context().Done()
  193. return nil, req.Context().Err()
  194. },
  195. },
  196. }),
  197. )
  198. cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
  199. defer cancel()
  200. stream := client.Event.ListStreaming(cancelCtx)
  201. for stream.Next() {
  202. // ...
  203. }
  204. err := stream.Err()
  205. if err == nil {
  206. t.Error("expected there to be a cancel error")
  207. }
  208. }
  209. func TestContextDeadline(t *testing.T) {
  210. testTimeout := time.After(3 * time.Second)
  211. testDone := make(chan struct{})
  212. deadline := time.Now().Add(100 * time.Millisecond)
  213. deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
  214. defer cancel()
  215. go func() {
  216. client := opencode.NewClient(
  217. option.WithHTTPClient(&http.Client{
  218. Transport: &closureTransport{
  219. fn: func(req *http.Request) (*http.Response, error) {
  220. <-req.Context().Done()
  221. return nil, req.Context().Err()
  222. },
  223. },
  224. }),
  225. )
  226. stream := client.Event.ListStreaming(deadlineCtx)
  227. for stream.Next() {
  228. // ...
  229. }
  230. err := stream.Err()
  231. if err == nil {
  232. t.Error("expected there to be a deadline error")
  233. }
  234. close(testDone)
  235. }()
  236. select {
  237. case <-testTimeout:
  238. t.Fatal("client didn't finish in time")
  239. case <-testDone:
  240. if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
  241. t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
  242. }
  243. }
  244. }
  245. func TestContextDeadlineStreaming(t *testing.T) {
  246. testTimeout := time.After(3 * time.Second)
  247. testDone := make(chan struct{})
  248. deadline := time.Now().Add(100 * time.Millisecond)
  249. deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
  250. defer cancel()
  251. go func() {
  252. client := opencode.NewClient(
  253. option.WithHTTPClient(&http.Client{
  254. Transport: &closureTransport{
  255. fn: func(req *http.Request) (*http.Response, error) {
  256. return &http.Response{
  257. StatusCode: 200,
  258. Status: "200 OK",
  259. Body: io.NopCloser(
  260. io.Reader(readerFunc(func([]byte) (int, error) {
  261. <-req.Context().Done()
  262. return 0, req.Context().Err()
  263. })),
  264. ),
  265. }, nil
  266. },
  267. },
  268. }),
  269. )
  270. stream := client.Event.ListStreaming(deadlineCtx)
  271. for stream.Next() {
  272. _ = stream.Current()
  273. }
  274. if stream.Err() == nil {
  275. t.Error("expected there to be a deadline error")
  276. }
  277. close(testDone)
  278. }()
  279. select {
  280. case <-testTimeout:
  281. t.Fatal("client didn't finish in time")
  282. case <-testDone:
  283. if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
  284. t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
  285. }
  286. }
  287. }
  288. func TestContextDeadlineStreamingWithRequestTimeout(t *testing.T) {
  289. testTimeout := time.After(3 * time.Second)
  290. testDone := make(chan struct{})
  291. deadline := time.Now().Add(100 * time.Millisecond)
  292. go func() {
  293. client := opencode.NewClient(
  294. option.WithHTTPClient(&http.Client{
  295. Transport: &closureTransport{
  296. fn: func(req *http.Request) (*http.Response, error) {
  297. return &http.Response{
  298. StatusCode: 200,
  299. Status: "200 OK",
  300. Body: io.NopCloser(
  301. io.Reader(readerFunc(func([]byte) (int, error) {
  302. <-req.Context().Done()
  303. return 0, req.Context().Err()
  304. })),
  305. ),
  306. }, nil
  307. },
  308. },
  309. }),
  310. )
  311. stream := client.Event.ListStreaming(context.Background(), option.WithRequestTimeout((100 * time.Millisecond)))
  312. for stream.Next() {
  313. _ = stream.Current()
  314. }
  315. if stream.Err() == nil {
  316. t.Error("expected there to be a deadline error")
  317. }
  318. close(testDone)
  319. }()
  320. select {
  321. case <-testTimeout:
  322. t.Fatal("client didn't finish in time")
  323. case <-testDone:
  324. if diff := time.Since(deadline); diff < -30*time.Millisecond || 30*time.Millisecond < diff {
  325. t.Fatalf("client did not return within 30ms of context deadline, got %s", diff)
  326. }
  327. }
  328. }
  329. type readerFunc func([]byte) (int, error)
  330. func (f readerFunc) Read(p []byte) (int, error) { return f(p) }
  331. func (f readerFunc) Close() error { return nil }