github.com/go-kivik/kivik/v4@v4.3.2/attachments_test.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 package kivik 14 15 import ( 16 "encoding/json" 17 "errors" 18 "io" 19 "net/http" 20 "strconv" 21 "strings" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "gitlab.com/flimzy/testy" 26 27 "github.com/go-kivik/kivik/v4/driver" 28 internal "github.com/go-kivik/kivik/v4/int/errors" 29 "github.com/go-kivik/kivik/v4/int/mock" 30 ) 31 32 func TestAttachmentMarshalJSON(t *testing.T) { 33 type tst struct { 34 att *Attachment 35 expected string 36 err string 37 } 38 tests := testy.NewTable() 39 tests.Add("foo.txt", tst{ 40 att: &Attachment{ 41 Content: io.NopCloser(strings.NewReader("test attachment\n")), 42 Filename: "foo.txt", 43 ContentType: "text/plain", 44 }, 45 expected: `{ 46 "content_type": "text/plain", 47 "data": "dGVzdCBhdHRhY2htZW50Cg==" 48 }`, 49 }) 50 tests.Add("revpos", tst{ 51 att: &Attachment{ 52 Content: io.NopCloser(strings.NewReader("test attachment\n")), 53 Filename: "foo.txt", 54 ContentType: "text/plain", 55 RevPos: 3, 56 }, 57 expected: `{ 58 "content_type": "text/plain", 59 "data": "dGVzdCBhdHRhY2htZW50Cg==", 60 "revpos": 3 61 }`, 62 }) 63 tests.Add("follows", tst{ 64 att: &Attachment{ 65 Content: io.NopCloser(strings.NewReader("test attachment\n")), 66 Filename: "foo.txt", 67 Follows: true, 68 ContentType: "text/plain", 69 RevPos: 3, 70 }, 71 expected: `{ 72 "content_type": "text/plain", 73 "follows": true, 74 "revpos": 3 75 }`, 76 }) 77 tests.Add("read error", tst{ 78 att: &Attachment{ 79 Content: io.NopCloser(&errorReader{}), 80 Filename: "foo.txt", 81 ContentType: "text/plain", 82 }, 83 err: "json: error calling MarshalJSON for type *kivik.Attachment: errorReader", 84 }) 85 tests.Add("stub", tst{ 86 att: &Attachment{ 87 Content: io.NopCloser(strings.NewReader("content")), 88 Stub: true, 89 Filename: "foo.txt", 90 ContentType: "text/plain", 91 Size: 7, 92 }, 93 expected: `{ 94 "content_type": "text/plain", 95 "length": 7, 96 "stub": true 97 }`, 98 }) 99 100 tests.Run(t, func(t *testing.T, test tst) { 101 result, err := json.Marshal(test.att) 102 if !testy.ErrorMatches(test.err, err) { 103 t.Errorf("Unexpected error: %s", err) 104 } 105 if d := testy.DiffJSON([]byte(test.expected), result); d != nil { 106 t.Error(d) 107 } 108 }) 109 } 110 111 func TestAttachmentUnmarshalJSON(t *testing.T) { 112 tests := []struct { 113 name string 114 input string 115 116 body string 117 expected *Attachment 118 err string 119 }{ 120 { 121 name: "stub", 122 input: `{ 123 "content_type": "text/plain", 124 "stub": true 125 }`, 126 expected: &Attachment{ 127 ContentType: "text/plain", 128 Stub: true, 129 }, 130 }, 131 { 132 name: "simple", 133 input: `{ 134 "content_type": "text/plain", 135 "data": "dGVzdCBhdHRhY2htZW50Cg==" 136 }`, 137 body: "test attachment\n", 138 expected: &Attachment{ 139 ContentType: "text/plain", 140 }, 141 }, 142 } 143 for _, test := range tests { 144 t.Run(test.name, func(t *testing.T) { 145 result := new(Attachment) 146 err := json.Unmarshal([]byte(test.input), result) 147 if !testy.ErrorMatches(test.err, err) { 148 t.Errorf("Unexpected error: %s", err) 149 } 150 var body []byte 151 content := result.Content 152 t.Cleanup(func() { 153 _ = content.Close() 154 }) 155 body, err = io.ReadAll(result.Content) 156 if err != nil { 157 t.Fatal(err) 158 } 159 result.Content = nil 160 if d := testy.DiffText(test.body, string(body)); d != nil { 161 t.Errorf("Unexpected body:\n%s", d) 162 } 163 if d := testy.DiffInterface(test.expected, result); d != nil { 164 t.Errorf("Unexpected result:\n%s", d) 165 } 166 }) 167 } 168 } 169 170 func TestAttachmentsUnmarshalJSON(t *testing.T) { 171 tests := []struct { 172 name string 173 input string 174 175 expected Attachments 176 err string 177 }{ 178 { 179 name: "no attachments", 180 input: "{}", 181 expected: Attachments{}, 182 }, 183 { 184 name: "one attachment", 185 input: `{ 186 "foo.txt": { 187 "content_type": "text/plain", 188 "data": "dGVzdCBhdHRhY2htZW50Cg==" 189 } 190 }`, 191 expected: Attachments{ 192 "foo.txt": &Attachment{ 193 Filename: "foo.txt", 194 ContentType: "text/plain", 195 }, 196 }, 197 }, 198 } 199 for _, test := range tests { 200 t.Run(test.name, func(t *testing.T) { 201 var att Attachments 202 err := json.Unmarshal([]byte(test.input), &att) 203 if !testy.ErrorMatches(test.err, err) { 204 t.Errorf("Unexpected error: %s", err) 205 } 206 for _, v := range att { 207 _ = v.Content.Close() 208 v.Content = nil 209 } 210 if d := testy.DiffInterface(test.expected, att); d != nil { 211 t.Error(d) 212 } 213 }) 214 } 215 } 216 217 func TestAttachmentsIteratorNext(t *testing.T) { 218 tests := []struct { 219 name string 220 iter *AttachmentsIterator 221 expected *Attachment 222 status int 223 err string 224 }{ 225 { 226 name: "error", 227 iter: &AttachmentsIterator{ 228 atti: &mock.Attachments{ 229 NextFunc: func(_ *driver.Attachment) error { 230 return &internal.Error{Status: http.StatusBadGateway, Err: errors.New("error")} 231 }, 232 }, 233 }, 234 status: http.StatusBadGateway, 235 err: "error", 236 }, 237 { 238 name: "success", 239 iter: &AttachmentsIterator{ 240 atti: &mock.Attachments{ 241 NextFunc: func(att *driver.Attachment) error { 242 *att = driver.Attachment{ 243 Filename: "foo.txt", 244 } 245 return nil 246 }, 247 }, 248 }, 249 expected: &Attachment{ 250 Filename: "foo.txt", 251 }, 252 }, 253 } 254 for _, test := range tests { 255 t.Run(test.name, func(t *testing.T) { 256 result, err := test.iter.Next() 257 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" { 258 t.Error(d) 259 } 260 if d := testy.DiffInterface(test.expected, result); d != nil { 261 t.Error(d) 262 } 263 }) 264 } 265 } 266 267 func TestAttachments_Next_resets_iterator_value(t *testing.T) { 268 idx := 0 269 atts := &AttachmentsIterator{ 270 atti: &mock.Attachments{ 271 NextFunc: func(att *driver.Attachment) error { 272 idx++ 273 switch idx { 274 case 1: 275 att.Filename = strconv.Itoa(idx) 276 return nil 277 case 2: 278 return nil 279 } 280 return io.EOF 281 }, 282 }, 283 } 284 285 wantFilenames := []string{"1", ""} 286 gotFilenames := []string{} 287 for { 288 att, err := atts.Next() 289 if err == io.EOF { 290 break 291 } 292 if err != nil { 293 t.Fatal(err) 294 } 295 gotFilenames = append(gotFilenames, att.Filename) 296 297 } 298 if d := cmp.Diff(wantFilenames, gotFilenames); d != "" { 299 t.Error(d) 300 } 301 }