amf_encoder_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. package handlers
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "testing"
  6. "time"
  7. goAMF3 "github.com/breign/goAMF3"
  8. )
  9. func TestAMFEncoderBasicTypes(t *testing.T) {
  10. encoder := NewAMFEncoder(nil)
  11. tests := []struct {
  12. name string
  13. input interface{}
  14. version AMFVersion
  15. wantErr bool
  16. }{
  17. {"String AMF3", "hello world", AMF3, false},
  18. {"Number AMF3", 42, AMF3, false},
  19. {"Float AMF3", 3.14159, AMF3, false},
  20. {"Boolean AMF3", false, AMF3, false},
  21. {"Null AMF3", nil, AMF3, false},
  22. }
  23. for _, tt := range tests {
  24. t.Run(tt.name, func(t *testing.T) {
  25. data, err := encoder.EncodeAMF(tt.input, tt.version)
  26. if (err != nil) != tt.wantErr {
  27. t.Fatalf("EncodeAMF() error = %v, wantErr %v", err, tt.wantErr)
  28. }
  29. if !tt.wantErr && len(data) == 0 {
  30. t.Fatal("EncodeAMF() returned empty data")
  31. }
  32. // Try to decode the data to verify it's valid AMF3
  33. if !tt.wantErr {
  34. decoded := goAMF3.DecodeAMF3(data)
  35. if decoded == nil {
  36. t.Fatalf("Failed to decode AMF3 data: got nil result")
  37. }
  38. }
  39. })
  40. }
  41. }
  42. func TestAMFEncoderComplexTypes(t *testing.T) {
  43. encoder := NewAMFEncoder(nil)
  44. tests := []struct {
  45. name string
  46. input interface{}
  47. version AMFVersion
  48. }{
  49. {
  50. name: "Map",
  51. input: map[string]interface{}{
  52. "name": "John Doe",
  53. "age": 30,
  54. "active": true,
  55. },
  56. version: AMF3,
  57. },
  58. {
  59. name: "Array",
  60. input: []interface{}{
  61. "item1",
  62. 42,
  63. true,
  64. nil,
  65. },
  66. version: AMF3,
  67. },
  68. {
  69. name: "BaseResponse",
  70. input: BaseResponse{
  71. Response: ResponseBody{
  72. StatusCode: 200,
  73. StatusText: "OK",
  74. Data: map[string]interface{}{
  75. "user": "testuser",
  76. "online": true,
  77. "buddies": []interface{}{
  78. "friend1",
  79. "friend2",
  80. },
  81. },
  82. },
  83. },
  84. version: AMF3,
  85. },
  86. {
  87. name: "ErrorResponse",
  88. input: newErrorResponse(404, "Not Found"),
  89. version: AMF3,
  90. },
  91. {
  92. name: "Time",
  93. input: map[string]interface{}{
  94. "timestamp": time.Now(),
  95. "name": "Event",
  96. },
  97. version: AMF3,
  98. },
  99. }
  100. for _, tt := range tests {
  101. t.Run(tt.name, func(t *testing.T) {
  102. data, err := encoder.EncodeAMF(tt.input, tt.version)
  103. if err != nil {
  104. t.Fatalf("EncodeAMF() error = %v", err)
  105. }
  106. if len(data) == 0 {
  107. t.Fatal("EncodeAMF() returned empty data")
  108. }
  109. // Verify the data is valid AMF
  110. decoded := goAMF3.DecodeAMF3(data)
  111. if decoded == nil {
  112. t.Fatalf("Failed to decode AMF data: got nil result")
  113. }
  114. // Log the size for performance comparison
  115. t.Logf("%s: %d bytes", tt.name, len(data))
  116. })
  117. }
  118. }
  119. func TestDetectAMFVersion(t *testing.T) {
  120. tests := []struct {
  121. name string
  122. request *http.Request
  123. expected AMFVersion
  124. }{
  125. {
  126. name: "Query parameter amf3",
  127. request: httptest.NewRequest("GET", "/?f=amf3", nil),
  128. expected: AMF3,
  129. },
  130. {
  131. name: "Query parameter amf",
  132. request: httptest.NewRequest("GET", "/?f=amf", nil),
  133. expected: AMF3,
  134. },
  135. {
  136. name: "Accept header AMF3",
  137. request: func() *http.Request {
  138. req := httptest.NewRequest("GET", "/", nil)
  139. req.Header.Set("Accept", "application/x-amf3")
  140. return req
  141. }(),
  142. expected: AMF3,
  143. },
  144. {
  145. name: "Accept header AMF",
  146. request: func() *http.Request {
  147. req := httptest.NewRequest("GET", "/", nil)
  148. req.Header.Set("Accept", "application/x-amf")
  149. return req
  150. }(),
  151. expected: AMF3,
  152. },
  153. {
  154. name: "No AMF indication",
  155. request: httptest.NewRequest("GET", "/", nil),
  156. expected: AMF3,
  157. },
  158. {
  159. name: "Nil request",
  160. request: nil,
  161. expected: AMF3,
  162. },
  163. }
  164. for _, tt := range tests {
  165. t.Run(tt.name, func(t *testing.T) {
  166. version := DetectAMFVersion(tt.request)
  167. if version != tt.expected {
  168. t.Errorf("DetectAMFVersion() = %v, want %v", version, tt.expected)
  169. }
  170. })
  171. }
  172. }
  173. func TestSendAMF(t *testing.T) {
  174. tests := []struct {
  175. name string
  176. request *http.Request
  177. data interface{}
  178. expectStatus int
  179. }{
  180. {
  181. name: "Simple response",
  182. request: httptest.NewRequest("GET", "/?f=amf", nil),
  183. data: BaseResponse{
  184. Response: ResponseBody{
  185. StatusCode: 200,
  186. StatusText: "OK",
  187. Data: map[string]interface{}{"test": "value"},
  188. },
  189. },
  190. expectStatus: http.StatusOK,
  191. },
  192. {
  193. name: "AMF3 response with array",
  194. request: httptest.NewRequest("GET", "/?f=amf3", nil),
  195. data: BaseResponse{
  196. Response: ResponseBody{
  197. StatusCode: 200,
  198. StatusText: "OK",
  199. Data: []interface{}{"item1", "item2"},
  200. },
  201. },
  202. expectStatus: http.StatusOK,
  203. },
  204. }
  205. for _, tt := range tests {
  206. t.Run(tt.name, func(t *testing.T) {
  207. // First test if the encoder can handle the data
  208. encoder := NewAMFEncoder(nil)
  209. version := DetectAMFVersion(tt.request)
  210. _, encodeErr := encoder.EncodeAMF(tt.data, version)
  211. if encodeErr != nil {
  212. t.Fatalf("Encoding failed: %v", encodeErr)
  213. }
  214. w := httptest.NewRecorder()
  215. sendAMF(w, tt.request, tt.data, nil)
  216. resp := w.Result()
  217. if resp.StatusCode != tt.expectStatus {
  218. t.Errorf("Expected status %d, got %d", tt.expectStatus, resp.StatusCode)
  219. // Print response body for debugging
  220. body := w.Body.String()
  221. t.Logf("Response body: %s", body)
  222. }
  223. contentType := resp.Header.Get("Content-Type")
  224. if contentType != "application/x-amf" {
  225. t.Errorf("Expected Content-Type application/x-amf, got %s", contentType)
  226. }
  227. body := w.Body.Bytes()
  228. if len(body) == 0 {
  229. t.Error("Response body is empty")
  230. }
  231. })
  232. }
  233. }
  234. func TestStructToMap(t *testing.T) {
  235. encoder := NewAMFEncoder(nil)
  236. type TestStruct struct {
  237. Name string `json:"name"`
  238. Age int `json:"age"`
  239. Active bool `json:"active"`
  240. Hidden string `json:"-"`
  241. Optional string `json:"optional,omitempty"`
  242. NoTag string
  243. }
  244. testStruct := TestStruct{
  245. Name: "John",
  246. Age: 30,
  247. Active: true,
  248. Hidden: "should not appear",
  249. Optional: "", // should be omitted
  250. NoTag: "should appear with field name",
  251. }
  252. result := encoder.toAMF3Compatible(testStruct)
  253. resultMap, ok := result.(map[string]interface{})
  254. if !ok {
  255. t.Fatal("Expected map[string]interface{}")
  256. }
  257. // Check expected fields
  258. if resultMap["name"] != "John" {
  259. t.Errorf("Expected name=John, got %v", resultMap["name"])
  260. }
  261. if resultMap["age"] != 30 {
  262. t.Errorf("Expected age=30, got %v", resultMap["age"])
  263. }
  264. if resultMap["active"] != true {
  265. t.Errorf("Expected active=true, got %v", resultMap["active"])
  266. }
  267. if resultMap["NoTag"] != "should appear with field name" {
  268. t.Errorf("Expected NoTag field, got %v", resultMap["NoTag"])
  269. }
  270. // Check omitted fields
  271. if _, exists := resultMap["Hidden"]; exists {
  272. t.Error("Hidden field should not appear")
  273. }
  274. if _, exists := resultMap["optional"]; exists {
  275. t.Error("Optional empty field should be omitted")
  276. }
  277. }
  278. func TestSliceToArray(t *testing.T) {
  279. encoder := NewAMFEncoder(nil)
  280. input := []interface{}{
  281. "string",
  282. 42,
  283. true,
  284. nil,
  285. map[string]interface{}{"nested": "value"},
  286. }
  287. result := encoder.toAMF3Compatible(input)
  288. resultArray, ok := result.([]interface{})
  289. if !ok {
  290. t.Fatal("Expected []interface{}")
  291. }
  292. if len(resultArray) != 5 {
  293. t.Errorf("Expected 5 elements, got %d", len(resultArray))
  294. }
  295. if resultArray[0] != "string" {
  296. t.Errorf("Expected first element to be 'string', got %v", resultArray[0])
  297. }
  298. if resultArray[1] != 42 {
  299. t.Errorf("Expected second element to be 42, got %v", resultArray[1])
  300. }
  301. if resultArray[2] != true {
  302. t.Errorf("Expected third element to be true, got %v", resultArray[2])
  303. }
  304. // For AMF3, nil values are converted to empty maps for compatibility
  305. if resultArray[3] != nil {
  306. emptyMap, ok := resultArray[3].(map[string]interface{})
  307. if !ok || len(emptyMap) != 0 {
  308. t.Errorf("Expected fourth element to be empty map, got %v", resultArray[3])
  309. }
  310. }
  311. nested, ok := resultArray[4].(map[string]interface{})
  312. if !ok {
  313. t.Error("Expected fifth element to be map")
  314. } else if nested["nested"] != "value" {
  315. t.Errorf("Expected nested value, got %v", nested["nested"])
  316. }
  317. }
  318. // Benchmark tests
  319. func BenchmarkAMFEncoding(b *testing.B) {
  320. encoder := NewAMFEncoder(nil)
  321. data := BaseResponse{
  322. Response: ResponseBody{
  323. StatusCode: 200,
  324. StatusText: "OK",
  325. Data: map[string]interface{}{
  326. "users": []interface{}{
  327. map[string]interface{}{"name": "user1", "online": true},
  328. map[string]interface{}{"name": "user2", "online": false},
  329. map[string]interface{}{"name": "user3", "online": true},
  330. },
  331. "timestamp": time.Now().Unix(),
  332. "server": "open-oscar-server",
  333. },
  334. },
  335. }
  336. b.Run("AMF3", func(b *testing.B) {
  337. for i := 0; i < b.N; i++ {
  338. _, _ = encoder.EncodeAMF(data, AMF3)
  339. }
  340. })
  341. }
  342. func TestZeroValueDetection(t *testing.T) {
  343. encoder := NewAMFEncoder(nil)
  344. type TestStruct struct {
  345. EmptyString string `json:"emptyString,omitempty"`
  346. ZeroInt int `json:"zeroInt,omitempty"`
  347. FalseValue bool `json:"falseValue,omitempty"`
  348. ZeroTime time.Time `json:"zeroTime,omitempty"`
  349. ValidString string `json:"validString,omitempty"`
  350. ValidInt int `json:"validInt,omitempty"`
  351. TrueValue bool `json:"trueValue,omitempty"`
  352. }
  353. testStruct := TestStruct{
  354. EmptyString: "",
  355. ZeroInt: 0,
  356. FalseValue: false,
  357. ZeroTime: time.Time{},
  358. ValidString: "test",
  359. ValidInt: 42,
  360. TrueValue: true,
  361. }
  362. result := encoder.toAMF3Compatible(testStruct)
  363. resultMap, ok := result.(map[string]interface{})
  364. if !ok {
  365. t.Fatal("Expected map[string]interface{}")
  366. }
  367. // Should be omitted (zero values)
  368. omittedFields := []string{"emptyString", "zeroInt", "falseValue", "zeroTime"}
  369. for _, field := range omittedFields {
  370. if _, exists := resultMap[field]; exists {
  371. t.Errorf("Field %s should be omitted (zero value)", field)
  372. }
  373. }
  374. // Should be present (non-zero values)
  375. presentFields := map[string]interface{}{
  376. "validString": "test",
  377. "validInt": 42,
  378. "trueValue": true,
  379. }
  380. for field, expected := range presentFields {
  381. if actual, exists := resultMap[field]; !exists {
  382. t.Errorf("Field %s should be present", field)
  383. } else if actual != expected {
  384. t.Errorf("Field %s: expected %v, got %v", field, expected, actual)
  385. }
  386. }
  387. }
  388. // The client dereferences response.data on a failure too, so the AMF error
  389. // envelope carries one just as the JSON, JSONP and XML ones do.
  390. func TestAMFErrorEnvelopeCarriesData(t *testing.T) {
  391. encoder := NewAMFEncoder(nil)
  392. out, ok := encoder.toAMF3Compatible(newErrorResponse(404, "Not Found")).(map[string]interface{})
  393. if !ok {
  394. t.Fatalf("expected an envelope map, got %T", out)
  395. }
  396. resp, ok := out["response"].(map[string]interface{})
  397. if !ok {
  398. t.Fatalf("expected a response map, got %T", out["response"])
  399. }
  400. if resp["statusCode"] != 404 {
  401. t.Errorf("statusCode: expected 404, got %v", resp["statusCode"])
  402. }
  403. data, ok := resp["data"].(map[string]interface{})
  404. if !ok {
  405. t.Fatalf("expected an empty data map, got %T", resp["data"])
  406. }
  407. if len(data) != 0 {
  408. t.Errorf("data: expected empty, got %v", data)
  409. }
  410. }