channel_repo_test.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. //go:build unit
  2. package repository
  3. import (
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "testing"
  8. "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
  9. "github.com/lib/pq"
  10. "github.com/stretchr/testify/require"
  11. )
  12. // --- marshalModelMapping ---
  13. func TestMarshalModelMapping(t *testing.T) {
  14. tests := []struct {
  15. name string
  16. input map[string]map[string]string
  17. wantJSON string // expected JSON output (exact match)
  18. }{
  19. {
  20. name: "empty map",
  21. input: map[string]map[string]string{},
  22. wantJSON: "{}",
  23. },
  24. {
  25. name: "nil map",
  26. input: nil,
  27. wantJSON: "{}",
  28. },
  29. {
  30. name: "populated map",
  31. input: map[string]map[string]string{
  32. "openai": {"gpt-4": "gpt-4-turbo"},
  33. },
  34. },
  35. {
  36. name: "nested values",
  37. input: map[string]map[string]string{
  38. "openai": {"*": "gpt-5.4"},
  39. "anthropic": {"claude-old": "claude-new"},
  40. },
  41. },
  42. }
  43. for _, tt := range tests {
  44. t.Run(tt.name, func(t *testing.T) {
  45. result, err := marshalModelMapping(tt.input)
  46. require.NoError(t, err)
  47. if tt.wantJSON != "" {
  48. require.Equal(t, []byte(tt.wantJSON), result)
  49. } else {
  50. // round-trip: unmarshal and compare with input
  51. var parsed map[string]map[string]string
  52. require.NoError(t, json.Unmarshal(result, &parsed))
  53. require.Equal(t, tt.input, parsed)
  54. }
  55. })
  56. }
  57. }
  58. // --- unmarshalModelMapping ---
  59. func TestUnmarshalModelMapping(t *testing.T) {
  60. tests := []struct {
  61. name string
  62. input []byte
  63. wantNil bool
  64. want map[string]map[string]string
  65. }{
  66. {
  67. name: "nil data",
  68. input: nil,
  69. wantNil: true,
  70. },
  71. {
  72. name: "empty data",
  73. input: []byte{},
  74. wantNil: true,
  75. },
  76. {
  77. name: "invalid JSON",
  78. input: []byte("not-json"),
  79. wantNil: true,
  80. },
  81. {
  82. name: "type error - number",
  83. input: []byte("42"),
  84. wantNil: true,
  85. },
  86. {
  87. name: "type error - array",
  88. input: []byte("[1,2,3]"),
  89. wantNil: true,
  90. },
  91. {
  92. name: "valid JSON",
  93. input: []byte(`{"openai":{"gpt-4":"gpt-4-turbo"},"anthropic":{"old":"new"}}`),
  94. want: map[string]map[string]string{
  95. "openai": {"gpt-4": "gpt-4-turbo"},
  96. "anthropic": {"old": "new"},
  97. },
  98. },
  99. {
  100. name: "empty object",
  101. input: []byte("{}"),
  102. want: map[string]map[string]string{},
  103. },
  104. }
  105. for _, tt := range tests {
  106. t.Run(tt.name, func(t *testing.T) {
  107. result := unmarshalModelMapping(tt.input)
  108. if tt.wantNil {
  109. require.Nil(t, result)
  110. } else {
  111. require.NotNil(t, result)
  112. require.Equal(t, tt.want, result)
  113. }
  114. })
  115. }
  116. }
  117. // --- escapeLike ---
  118. func TestEscapeLike(t *testing.T) {
  119. tests := []struct {
  120. name string
  121. input string
  122. want string
  123. }{
  124. {
  125. name: "no special chars",
  126. input: "hello",
  127. want: "hello",
  128. },
  129. {
  130. name: "backslash",
  131. input: `a\b`,
  132. want: `a\\b`,
  133. },
  134. {
  135. name: "percent",
  136. input: "50%",
  137. want: `50\%`,
  138. },
  139. {
  140. name: "underscore",
  141. input: "a_b",
  142. want: `a\_b`,
  143. },
  144. {
  145. name: "all special chars",
  146. input: `a\b%c_d`,
  147. want: `a\\b\%c\_d`,
  148. },
  149. {
  150. name: "empty string",
  151. input: "",
  152. want: "",
  153. },
  154. {
  155. name: "consecutive special chars",
  156. input: "%_%",
  157. want: `\%\_\%`,
  158. },
  159. }
  160. for _, tt := range tests {
  161. t.Run(tt.name, func(t *testing.T) {
  162. require.Equal(t, tt.want, escapeLike(tt.input))
  163. })
  164. }
  165. }
  166. // --- isUniqueViolation ---
  167. func TestIsUniqueViolation(t *testing.T) {
  168. tests := []struct {
  169. name string
  170. err error
  171. want bool
  172. }{
  173. {
  174. name: "unique violation code 23505",
  175. err: &pq.Error{Code: "23505"},
  176. want: true,
  177. },
  178. {
  179. name: "different pq error code",
  180. err: &pq.Error{Code: "23503"},
  181. want: false,
  182. },
  183. {
  184. name: "non-pq error",
  185. err: errors.New("some generic error"),
  186. want: false,
  187. },
  188. {
  189. name: "typed nil pq.Error",
  190. err: func() error {
  191. var pqErr *pq.Error
  192. return pqErr
  193. }(),
  194. want: false,
  195. },
  196. {
  197. name: "bare nil",
  198. err: nil,
  199. want: false,
  200. },
  201. {
  202. name: "wrapped pq error with 23505",
  203. err: fmt.Errorf("wrapped: %w", &pq.Error{Code: "23505"}),
  204. want: true,
  205. },
  206. }
  207. for _, tt := range tests {
  208. t.Run(tt.name, func(t *testing.T) {
  209. require.Equal(t, tt.want, isUniqueViolation(tt.err))
  210. })
  211. }
  212. }
  213. func TestChannelListOrderBy_AllowsDescendingIDSort(t *testing.T) {
  214. params := pagination.PaginationParams{
  215. SortBy: "id",
  216. SortOrder: "desc",
  217. }
  218. require.Equal(t, "c.id DESC, c.id DESC", channelListOrderBy(params))
  219. }