| 1 | // Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module s3 |
| 5 | |
| 6 | fn test_parse_s3_url_full() { |
| 7 | bucket, key := parse_s3_url('s3://my-bucket/path/to/key.txt') or { panic(err) } |
| 8 | assert bucket == 'my-bucket' |
| 9 | assert key == 'path/to/key.txt' |
| 10 | } |
| 11 | |
| 12 | fn test_parse_s3_url_only_key() { |
| 13 | bucket, key := parse_s3_url('s3://standalone-key') or { panic(err) } |
| 14 | assert bucket == '' |
| 15 | assert key == 'standalone-key' |
| 16 | } |
| 17 | |
| 18 | fn test_parse_s3_url_rejects_other_schemes() { |
| 19 | if _, _ := parse_s3_url('https://example.com/x') { |
| 20 | assert false, 'should have errored' |
| 21 | } |
| 22 | if _, _ := parse_s3_url('http://x') { |
| 23 | assert false, 'should have errored' |
| 24 | } |
| 25 | if _, _ := parse_s3_url('') { |
| 26 | assert false, 'should have errored' |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | fn test_parse_s3_url_rejects_empty_key() { |
| 31 | if _, _ := parse_s3_url('s3://my-bucket/') { |
| 32 | assert false, 'should have errored' |
| 33 | } |
| 34 | if _, _ := parse_s3_url('s3://') { |
| 35 | assert false, 'should have errored' |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | fn test_redact_url_strips_query() { |
| 40 | assert redact_url('https://x.y/path?X-Amz-Signature=abc') == 'https://x.y/path?<redacted>' |
| 41 | assert redact_url('https://x.y/path') == 'https://x.y/path' |
| 42 | } |
| 43 | |
| 44 | fn test_fetch_rejects_non_s3_scheme() { |
| 45 | if _ := fetch('https://example.com/x') { |
| 46 | assert false |
| 47 | } |
| 48 | if _ := fetch('http://example.com/x') { |
| 49 | assert false |
| 50 | } |
| 51 | if _ := fetch('file:///etc/passwd') { |
| 52 | assert false |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | fn test_fetch_rejects_post_method() { |
| 57 | // POST is not part of the s3.fetch contract: object writes use PUT, |
| 58 | // and POST in S3 is reserved for multipart-control endpoints which |
| 59 | // take an XML body that fetch does not generate. Reject explicitly |
| 60 | // rather than silently rewriting to PUT. |
| 61 | if _ := fetch('s3://my-bucket/key.txt', method: .post, body: 'x'.bytes()) { |
| 62 | assert false, 'POST should be rejected' |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | fn test_build_object_path_preserves_leading_and_trailing_slashes() { |
| 67 | creds := Credentials{ |
| 68 | access_key_id: 'K' |
| 69 | secret_access_key: 'S' |
| 70 | bucket: 'b' |
| 71 | } |
| 72 | // S3 keys are byte-exact: `folder/`, `/x` and `a//b` must round-trip |
| 73 | // untouched (only percent-encoded). Stripping slashes would address a |
| 74 | // different object than requested. |
| 75 | assert build_object_path(creds, '', 'folder/')! == '/b/folder/' |
| 76 | assert build_object_path(creds, '', '/x')! == '/b//x' |
| 77 | assert build_object_path(creds, '', 'a//b')! == '/b/a//b' |
| 78 | assert build_object_path(creds, '', 'plain.txt')! == '/b/plain.txt' |
| 79 | } |
| 80 | |