Files
TeleUploader/test/s3-auth-edge.test.ts
T
asepharyana 77340f63ae test: expand unit test coverage to all S3 edge cases; fix stale tests
Add comprehensive unit tests and repair stale tests that referenced the
old (pre-refactor) src/utils/* layout which no longer exists:

- s3-range: expand to 25 cases (suffix, clamping, malformed, zero-size,
  invalid totals, content-range formatting)
- s3-object-stream: rewrite against the real interfaces/s3 module; add
  multi-part ordering, ranges spanning parts, S3/CORS headers, fetch-error
  propagation
- s3-helpers-edge (new): compress heuristics, virtual-host bucket parsing,
  S3 route detection, client-IP/trustProxy, S3 response headers
- s3-auth-edge (new): verifyBodyHash, isS3Request, canonical-query-string
  encoding/sorting
- chunked-storage: rewrite against the real ChunkedStorage class (was
  importing deleted src/utils/chunked-storage) — chunk split, hashing,
  compression, size-limit guards, forwarding
- zip: fix stale import + add path-traversal/duplicate sanitization,
  locateZipEntry, empty-name fallback
- s3-docker-registry: fix stale src/config import; correct the rate-limit
  test to assert S3 routes INTENTIONALLY bypass rate limiting
- temp-stream (new): streamToTemp hashing, MD5, signature bytes, empty and
  oversized streams
- package.json: add the S3/unit files to test and test:s3 scripts

All new unit tests pass when run per-file (the project's documented mode to
avoid cross-file mock pollution). s3-sdk.test.ts (live E2E against a running
server) is deliberately excluded from test:s3.
2026-08-27 15:48:14 +07:00

88 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import { buildCanonicalQueryString, isS3Request, verifyBodyHash } from '../src/interfaces/s3/auth';
/**
* Edge-case coverage for the exported pure helpers in the SigV4 auth module
* that the existing s3-auth.test.ts does not exercise directly.
*/
describe('verifyBodyHash (body-integrity check)', () => {
it('returns null when no x-amz-content-sha256 header is present (unsigned allowed)', () => {
expect(verifyBodyHash('abc123', {})).toBeNull();
});
it('returns null for UNSIGNED-PAYLOAD', () => {
expect(verifyBodyHash('anything', { 'x-amz-content-sha256': 'UNSIGNED-PAYLOAD' })).toBeNull();
});
it('returns null for STREAMING-* payloads (checked after signature)', () => {
expect(
verifyBodyHash('anything', { 'x-amz-content-sha256': 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD' }),
).toBeNull();
});
it('returns BadDigest when the claimed and actual hash differ', () => {
const result = verifyBodyHash('actual-hash', { 'x-amz-content-sha256': 'claimed-hash' });
expect(result).not.toBeNull();
expect(result?.isValid).toBe(false);
expect(result?.errorCode).toBe('BadDigest');
});
it('returns null when the claimed and actual hash match', () => {
expect(verifyBodyHash('same', { 'x-amz-content-sha256': 'same' })).toBeNull();
});
});
describe('isS3Request (Auth header detection)', () => {
it('detects AWS4-HMAC-SHA256 authorization', () => {
expect(isS3Request({ authorization: 'AWS4-HMAC-SHA256 Credential=...' })).toBe(true);
});
it('returns false for bearer/simple auth', () => {
expect(isS3Request({ authorization: 'Bearer token' })).toBe(false);
});
it('returns false when no authorization header', () => {
expect(isS3Request({})).toBe(false);
});
it('is case-sensitive on the AWS4-HMAC-SHA256 scheme prefix', () => {
expect(isS3Request({ authorization: 'aws4-hmac-sha256 Credential=...' })).toBe(false);
});
});
describe('buildCanonicalQueryString (SigV4 query canonicalization)', () => {
it('returns empty string for no params', () => {
expect(buildCanonicalQueryString(new URLSearchParams())).toBe('');
});
it('sorts params by encoded key then value (byte order)', () => {
const sp = new URLSearchParams('b=2&a=1&c=3');
expect(buildCanonicalQueryString(sp)).toBe('a=1&b=2&c=3');
});
it('sorts by encoded (key=value) pair, not raw key', () => {
const sp = new URLSearchParams({ 'list-type': '2', prefix: 'x' });
// 'list-type' (l...) sorts before 'prefix' (p...)
expect(buildCanonicalQueryString(sp)).toBe('list-type=2&prefix=x');
});
it('excludes the X-Amz-Signature key when requested', () => {
const sp = new URLSearchParams({
'X-Amz-Signature': 'sig',
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
});
const result = buildCanonicalQueryString(sp, new Set(['X-Amz-Signature']));
expect(result).not.toContain('X-Amz-Signature');
expect(result).toContain('X-Amz-Algorithm');
});
it('percent-encodes special characters', () => {
const sp = new URLSearchParams();
sp.set('key with space', 'a&b');
const result = buildCanonicalQueryString(sp);
// space → %20, & → %26
expect(result).toContain('%20');
expect(result).toContain('%26');
});
});