amf_encoder_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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: ErrorResponse{
  89. Response: struct {
  90. StatusCode int `json:"statusCode" xml:"statusCode"`
  91. StatusText string `json:"statusText" xml:"statusText"`
  92. }{
  93. StatusCode: 404,
  94. StatusText: "Not Found",
  95. },
  96. },
  97. version: AMF3,
  98. },
  99. {
  100. name: "Time",
  101. input: map[string]interface{}{
  102. "timestamp": time.Now(),
  103. "name": "Event",
  104. },
  105. version: AMF3,
  106. },
  107. }
  108. for _, tt := range tests {
  109. t.Run(tt.name, func(t *testing.T) {
  110. data, err := encoder.EncodeAMF(tt.input, tt.version)
  111. if err != nil {
  112. t.Fatalf("EncodeAMF() error = %v", err)
  113. }
  114. if len(data) == 0 {
  115. t.Fatal("EncodeAMF() returned empty data")
  116. }
  117. // Verify the data is valid AMF
  118. decoded := goAMF3.DecodeAMF3(data)
  119. if decoded == nil {
  120. t.Fatalf("Failed to decode AMF data: got nil result")
  121. }
  122. // Log the size for performance comparison
  123. t.Logf("%s: %d bytes", tt.name, len(data))
  124. })
  125. }
  126. }
  127. func TestDetectAMFVersion(t *testing.T) {
  128. tests := []struct {
  129. name string
  130. request *http.Request
  131. expected AMFVersion
  132. }{
  133. {
  134. name: "Query parameter amf3",
  135. request: httptest.NewRequest("GET", "/?f=amf3", nil),
  136. expected: AMF3,
  137. },
  138. {
  139. name: "Query parameter amf",
  140. request: httptest.NewRequest("GET", "/?f=amf", nil),
  141. expected: AMF3,
  142. },
  143. {
  144. name: "Accept header AMF3",
  145. request: func() *http.Request {
  146. req := httptest.NewRequest("GET", "/", nil)
  147. req.Header.Set("Accept", "application/x-amf3")
  148. return req
  149. }(),
  150. expected: AMF3,
  151. },
  152. {
  153. name: "Accept header AMF",
  154. request: func() *http.Request {
  155. req := httptest.NewRequest("GET", "/", nil)
  156. req.Header.Set("Accept", "application/x-amf")
  157. return req
  158. }(),
  159. expected: AMF3,
  160. },
  161. {
  162. name: "No AMF indication",
  163. request: httptest.NewRequest("GET", "/", nil),
  164. expected: AMF3,
  165. },
  166. {
  167. name: "Nil request",
  168. request: nil,
  169. expected: AMF3,
  170. },
  171. }
  172. for _, tt := range tests {
  173. t.Run(tt.name, func(t *testing.T) {
  174. version := DetectAMFVersion(tt.request)
  175. if version != tt.expected {
  176. t.Errorf("DetectAMFVersion() = %v, want %v", version, tt.expected)
  177. }
  178. })
  179. }
  180. }
  181. func TestSendAMF(t *testing.T) {
  182. tests := []struct {
  183. name string
  184. request *http.Request
  185. data interface{}
  186. expectStatus int
  187. }{
  188. {
  189. name: "Simple response",
  190. request: httptest.NewRequest("GET", "/?f=amf", nil),
  191. data: BaseResponse{
  192. Response: ResponseBody{
  193. StatusCode: 200,
  194. StatusText: "OK",
  195. Data: map[string]interface{}{"test": "value"},
  196. },
  197. },
  198. expectStatus: http.StatusOK,
  199. },
  200. {
  201. name: "AMF3 response with array",
  202. request: httptest.NewRequest("GET", "/?f=amf3", nil),
  203. data: BaseResponse{
  204. Response: ResponseBody{
  205. StatusCode: 200,
  206. StatusText: "OK",
  207. Data: []interface{}{"item1", "item2"},
  208. },
  209. },
  210. expectStatus: http.StatusOK,
  211. },
  212. }
  213. for _, tt := range tests {
  214. t.Run(tt.name, func(t *testing.T) {
  215. // First test if the encoder can handle the data
  216. encoder := NewAMFEncoder(nil)
  217. version := DetectAMFVersion(tt.request)
  218. _, encodeErr := encoder.EncodeAMF(tt.data, version)
  219. if encodeErr != nil {
  220. t.Fatalf("Encoding failed: %v", encodeErr)
  221. }
  222. w := httptest.NewRecorder()
  223. sendAMF(w, tt.request, tt.data, nil)
  224. resp := w.Result()
  225. if resp.StatusCode != tt.expectStatus {
  226. t.Errorf("Expected status %d, got %d", tt.expectStatus, resp.StatusCode)
  227. // Print response body for debugging
  228. body := w.Body.String()
  229. t.Logf("Response body: %s", body)
  230. }
  231. contentType := resp.Header.Get("Content-Type")
  232. if contentType != "application/x-amf" {
  233. t.Errorf("Expected Content-Type application/x-amf, got %s", contentType)
  234. }
  235. body := w.Body.Bytes()
  236. if len(body) == 0 {
  237. t.Error("Response body is empty")
  238. }
  239. })
  240. }
  241. }
  242. func TestStructToMap(t *testing.T) {
  243. encoder := NewAMFEncoder(nil)
  244. type TestStruct struct {
  245. Name string `json:"name"`
  246. Age int `json:"age"`
  247. Active bool `json:"active"`
  248. Hidden string `json:"-"`
  249. Optional string `json:"optional,omitempty"`
  250. NoTag string
  251. }
  252. testStruct := TestStruct{
  253. Name: "John",
  254. Age: 30,
  255. Active: true,
  256. Hidden: "should not appear",
  257. Optional: "", // should be omitted
  258. NoTag: "should appear with field name",
  259. }
  260. result := encoder.toAMF3Compatible(testStruct)
  261. resultMap, ok := result.(map[string]interface{})
  262. if !ok {
  263. t.Fatal("Expected map[string]interface{}")
  264. }
  265. // Check expected fields
  266. if resultMap["name"] != "John" {
  267. t.Errorf("Expected name=John, got %v", resultMap["name"])
  268. }
  269. if resultMap["age"] != 30 {
  270. t.Errorf("Expected age=30, got %v", resultMap["age"])
  271. }
  272. if resultMap["active"] != true {
  273. t.Errorf("Expected active=true, got %v", resultMap["active"])
  274. }
  275. if resultMap["NoTag"] != "should appear with field name" {
  276. t.Errorf("Expected NoTag field, got %v", resultMap["NoTag"])
  277. }
  278. // Check omitted fields
  279. if _, exists := resultMap["Hidden"]; exists {
  280. t.Error("Hidden field should not appear")
  281. }
  282. if _, exists := resultMap["optional"]; exists {
  283. t.Error("Optional empty field should be omitted")
  284. }
  285. }
  286. func TestSliceToArray(t *testing.T) {
  287. encoder := NewAMFEncoder(nil)
  288. input := []interface{}{
  289. "string",
  290. 42,
  291. true,
  292. nil,
  293. map[string]interface{}{"nested": "value"},
  294. }
  295. result := encoder.toAMF3Compatible(input)
  296. resultArray, ok := result.([]interface{})
  297. if !ok {
  298. t.Fatal("Expected []interface{}")
  299. }
  300. if len(resultArray) != 5 {
  301. t.Errorf("Expected 5 elements, got %d", len(resultArray))
  302. }
  303. if resultArray[0] != "string" {
  304. t.Errorf("Expected first element to be 'string', got %v", resultArray[0])
  305. }
  306. if resultArray[1] != 42 {
  307. t.Errorf("Expected second element to be 42, got %v", resultArray[1])
  308. }
  309. if resultArray[2] != true {
  310. t.Errorf("Expected third element to be true, got %v", resultArray[2])
  311. }
  312. // For AMF3, nil values are converted to empty maps for compatibility
  313. if resultArray[3] != nil {
  314. emptyMap, ok := resultArray[3].(map[string]interface{})
  315. if !ok || len(emptyMap) != 0 {
  316. t.Errorf("Expected fourth element to be empty map, got %v", resultArray[3])
  317. }
  318. }
  319. nested, ok := resultArray[4].(map[string]interface{})
  320. if !ok {
  321. t.Error("Expected fifth element to be map")
  322. } else if nested["nested"] != "value" {
  323. t.Errorf("Expected nested value, got %v", nested["nested"])
  324. }
  325. }
  326. // Benchmark tests
  327. func BenchmarkAMFEncoding(b *testing.B) {
  328. encoder := NewAMFEncoder(nil)
  329. data := BaseResponse{
  330. Response: ResponseBody{
  331. StatusCode: 200,
  332. StatusText: "OK",
  333. Data: map[string]interface{}{
  334. "users": []interface{}{
  335. map[string]interface{}{"name": "user1", "online": true},
  336. map[string]interface{}{"name": "user2", "online": false},
  337. map[string]interface{}{"name": "user3", "online": true},
  338. },
  339. "timestamp": time.Now().Unix(),
  340. "server": "open-oscar-server",
  341. },
  342. },
  343. }
  344. b.Run("AMF3", func(b *testing.B) {
  345. for i := 0; i < b.N; i++ {
  346. _, _ = encoder.EncodeAMF(data, AMF3)
  347. }
  348. })
  349. }
  350. func TestZeroValueDetection(t *testing.T) {
  351. encoder := NewAMFEncoder(nil)
  352. type TestStruct struct {
  353. EmptyString string `json:"emptyString,omitempty"`
  354. ZeroInt int `json:"zeroInt,omitempty"`
  355. FalseValue bool `json:"falseValue,omitempty"`
  356. ZeroTime time.Time `json:"zeroTime,omitempty"`
  357. ValidString string `json:"validString,omitempty"`
  358. ValidInt int `json:"validInt,omitempty"`
  359. TrueValue bool `json:"trueValue,omitempty"`
  360. }
  361. testStruct := TestStruct{
  362. EmptyString: "",
  363. ZeroInt: 0,
  364. FalseValue: false,
  365. ZeroTime: time.Time{},
  366. ValidString: "test",
  367. ValidInt: 42,
  368. TrueValue: true,
  369. }
  370. result := encoder.toAMF3Compatible(testStruct)
  371. resultMap, ok := result.(map[string]interface{})
  372. if !ok {
  373. t.Fatal("Expected map[string]interface{}")
  374. }
  375. // Should be omitted (zero values)
  376. omittedFields := []string{"emptyString", "zeroInt", "falseValue", "zeroTime"}
  377. for _, field := range omittedFields {
  378. if _, exists := resultMap[field]; exists {
  379. t.Errorf("Field %s should be omitted (zero value)", field)
  380. }
  381. }
  382. // Should be present (non-zero values)
  383. presentFields := map[string]interface{}{
  384. "validString": "test",
  385. "validInt": 42,
  386. "trueValue": true,
  387. }
  388. for field, expected := range presentFields {
  389. if actual, exists := resultMap[field]; !exists {
  390. t.Errorf("Field %s should be present", field)
  391. } else if actual != expected {
  392. t.Errorf("Field %s: expected %v, got %v", field, expected, actual)
  393. }
  394. }
  395. }