4
0

amf_encoder_test.go 13 KB

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