原创内容,未获授权禁止转载、转发、抄袭。
文件上传看起来只是选择文件、点击提交,背后却会经过浏览器或客户端、网关、业务服务、对象存储、病毒扫描和下载接口。任何一层处理不一致,都可能留下难复现的问题:前端提示上传成功,存储里只有半个文件;扩展名校验通过,实际内容却是脚本;同一请求重试两次,附件数量和存储对象都翻倍;用户退出项目后,仍能通过旧地址下载附件。
本文用"报销单附件"作为贯穿案例。接口允许上传 PDF、PNG 和 JPEG,单文件不超过 10 MiB,每张报销单最多 5 个附件;文件先进入隔离区,扫描通过后才能下载。下面不罗列一张庞大的测试点清单,而是从一批可复用的测试文件出发,完整走一遍上传、存储、扫描和下载链路。域名、账号、文件和接口均为演示数据,命令只应在授权的测试环境执行。
先拿到一份能判定结果的契约
本文示例接口约定如下:
http
POST /api/claims/{claimId}/attachments HTTP/1.1
Authorization: Bearer <access-token>
Content-Type: multipart/form-data; boundary=...
file=<binary>
上传成功返回 201,文件状态先置为 SCANNING:
json
{
"attachmentId": "att-10001",
"originalName": "invoice.png",
"size": 6821,
"mediaType": "image/png",
"sha256": "<digest>",
"status": "SCANNING"
}
测试前需要和产品、开发确认这些规则:
- 大小单位是 10 MiB(
10 * 1024 * 1024字节),还是 10 MB(10 * 1000 * 1000字节); - 允许格式按扩展名、请求
Content-Type、文件签名还是内容解析结果判断; - 同名文件是允许共存、覆盖旧文件,还是返回重复错误;
- 第 6 个附件是整次请求失败,还是上传成功后删除最早文件;
- 扫描失败、超时和服务不可用时,文件能否下载、是否自动重试;
- 删除附件后,对象存储何时清理,审计记录保留多久。
这些规则没有结论时,测试只能记录现象,不能自行把某种实现判为正确。本文采用"服务端生成存储名、同名可共存、第 6 个附件返回 ATTACHMENT_LIMIT_EXCEEDED、扫描通过后才能下载"的契约。
准备一组能重复使用的文件
边界文件最好由项目认可的工具生成一次,放进团队测试样本库,并在每次执行前校验字节数和解析结果。直接给普通文件补零可能破坏 PDF 或图片结构,得到的只是"扩展名正确"的无效文件。先建立独立目录并准备异常样本:
bash
set -eu
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
# 零字节异常样本。
: > "$WORK_DIR/empty.pdf"
# 扩展名与内容不一致。
printf '%s\n' '<script>alert(1)</script>' > "$WORK_DIR/fake-image.jpg"
printf '%s\n' 'plain text' > "$WORK_DIR/invoice.php.jpg"
# 文件名边界。
printf '%s\n' 'test' > "$WORK_DIR/报销单 2026 (终版).txt"
printf '%s\n' 'test' > "$WORK_DIR/name-without-extension"
wc -c "$WORK_DIR"/*
从团队维护的测试样本库复制三份普通文件和三份大小边界 PDF。不要把普通文本改个扩展名当作正常样本:
bash
cp /path/to/test-assets/invoice.png "$WORK_DIR/invoice.png"
cp /path/to/test-assets/invoice.jpg "$WORK_DIR/invoice.jpg"
cp /path/to/test-assets/invoice.pdf "$WORK_DIR/invoice.pdf"
cp /path/to/test-assets/invoice-under-limit.pdf "$WORK_DIR/invoice-under-limit.pdf"
cp /path/to/test-assets/invoice-at-limit.pdf "$WORK_DIR/invoice-at-limit.pdf"
cp /path/to/test-assets/invoice-over-limit.pdf "$WORK_DIR/invoice-over-limit.pdf"
上传前校验三个边界文件的精确字节数,并确认它们都能被项目 PDF 解析器打开:
bash
check_pdf() {
ACTUAL_SIZE=$(wc -c < "$1" | tr -d ' ')
test "$ACTUAL_SIZE" = "$2"
test "$(file --mime-type -b "$1")" = 'application/pdf'
}
check_pdf "$WORK_DIR/invoice-under-limit.pdf" 10485759
check_pdf "$WORK_DIR/invoice-at-limit.pdf" 10485760
check_pdf "$WORK_DIR/invoice-over-limit.pdf" 10485761
file 只能做基础识别,不能代替业务使用的 PDF 解析器;团队生成样本时还应跑一次项目解析流程并保存结果。普通合法文件上传前同样记录大小和摘要:
bash
FILE="$WORK_DIR/invoice.png"
wc -c "$FILE"
shasum -a 256 "$FILE"
file --mime-type "$FILE"
测试文件目录不要混入真实身份证、发票或患者资料。需要验证隐私数据时,使用合成内容,并在测试完成后清理对象存储和本地临时文件。
正常上传要核对四层结果
先跑通一份合法 PNG。让 curl 生成 multipart boundary,不要手工写 Content-Type,否则 boundary 与请求体不一致时,测到的是构造错误:
bash
API_URL='https://api.example.test'
CLAIM_ID='claim-upload-normal'
FILE="$WORK_DIR/invoice.png"
CURL_AUTH_CONFIG="$WORK_DIR/curl-auth.conf"
read -r -s -p '测试 Access Token: ' ACCESS_TOKEN
printf '\n'
umask 077
printf 'header = "Authorization: Bearer %s"\n' "$ACCESS_TOKEN" > "$CURL_AUTH_CONFIG"
unset ACCESS_TOKEN
HTTP_CODE=$(curl -sS --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$FILE;type=image/png" \
-o "$WORK_DIR/upload.json" \
-w '%{http_code}')
test "$HTTP_CODE" = '201'
ATTACHMENT_ID=$(jq -er '.attachmentId' "$WORK_DIR/upload.json")
SERVER_SHA256=$(jq -er '.sha256' "$WORK_DIR/upload.json")
LOCAL_SHA256=$(shasum -a 256 "$FILE" | awk '{print $1}')
test "$SERVER_SHA256" = "$LOCAL_SHA256"
jq '{attachmentId, originalName, size, mediaType, sha256, status}' \
"$WORK_DIR/upload.json"
认证头保存在权限受限的临时配置中,避免 Token 出现在命令行参数和 Shell 历史里;退出当前终端时会随 $WORK_DIR 一起删除。后续命令复用 CURL_AUTH_CONFIG,如果分开执行代码块,需要重新设置这些变量。
一次正常上传至少要核对四层:
| 层次 | 检查内容 |
|---|---|
| HTTP | 返回 201,附件 ID 唯一,文件名、大小和媒体类型符合契约 |
| 元数据 | 报销单、上传用户、对象 Key、摘要和 SCANNING 状态正确 |
| 对象存储 | 对象存在、长度一致,Key 由服务端生成且存储桶不公开 |
| 异步扫描 | 状态最终变为 AVAILABLE,期间下载接口拒绝访问 |
异步状态不能靠固定休眠猜测。先在 SCANNING 状态请求下载,本文契约要求返回 423 和 FILE_SCANNING,且不能返回文件内容:
bash
PRE_SCAN_HTTP=$(curl -sS --config "$CURL_AUTH_CONFIG" \
-D "$WORK_DIR/pre-scan.headers" \
-o "$WORK_DIR/pre-scan.json" \
-w '%{http_code}' \
"$API_URL/api/attachments/$ATTACHMENT_ID/content")
test "$PRE_SCAN_HTTP" = '423'
grep -qi '^content-type: application/json' "$WORK_DIR/pre-scan.headers"
jq -e '.code == "FILE_SCANNING"' "$WORK_DIR/pre-scan.json" >/dev/null
确认拦截后,再在 30 秒范围内轮询附件状态。项目应按扫描服务的实际 SLA 调整超时:
bash
DEADLINE=$((SECONDS + 30))
while :; do
STATUS=$(curl -sS --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/attachments/$ATTACHMENT_ID" | jq -er '.status')
case "$STATUS" in
AVAILABLE) break ;;
REJECTED|FAILED) printf 'scan ended with %s\n' "$STATUS" >&2; exit 1 ;;
esac
test "$SECONDS" -lt "$DEADLINE" || {
printf 'scan timeout, last status=%s\n' "$STATUS" >&2
exit 1
}
sleep 1
done
状态变为 AVAILABLE 后再下载并比较摘要,能发现传输截断、错误转码和对象串用:
bash
curl -sS --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/attachments/$ATTACHMENT_ID/content" \
-o "$WORK_DIR/downloaded.png"
shasum -a 256 "$FILE" "$WORK_DIR/downloaded.png"
cmp "$FILE" "$WORK_DIR/downloaded.png"
如果响应带有服务端计算的 sha256,还要与本地摘要比较。只验证页面缩略图能显示,会漏掉原文件损坏、元数据关联错误和对象存储公开访问等问题。
正常链路跑通后,把 CLAIM_ID 换成另一个用户、另一个租户、已关闭和不存在的报销单,使用同一文件再次上传。服务端应在写对象前完成目标资源鉴权;失败请求不能创建附件元数据、临时对象、扫描任务或"上传失败"的空记录。只验证下载权限,会漏掉攻击者向他人业务单据写入文件的风险。
扩展名、MIME 和内容要交叉测试
浏览器文件选择框的 accept 只能帮助用户筛选,不能作为服务端安全校验。用 curl 可以绕过前端限制,分别改变文件名、multipart 的媒体类型和真实内容:
bash
CLAIM_ID='claim-upload-format'
# 文本内容伪装成 JPEG。
curl -sS -i --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/fake-image.jpg;type=image/jpeg"
# 合法 PNG 使用错误的请求媒体类型。
curl -sS -i --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/invoice.png;type=text/plain"
# 双扩展名。
curl -sS -i --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/invoice.php.jpg;type=image/jpeg"
本文契约要求服务端同时检查扩展名允许列表、文件签名和内容解析结果,因此三类伪装都不能进入可下载状态。失败后继续查副作用:附件数量不增加,没有可访问对象,没有触发后续 OCR 或缩略图任务,日志中也不保存完整文件内容。
页面通常只会发送一个 file 字段,直接调用接口还要补三种请求:
bash
# 零字节文件。
curl -sS -i --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/empty.pdf;type=application/pdf"
# multipart 中没有 file 字段。
curl -sS -i --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F 'description=missing-file'
# 同一请求重复两个 file 字段。
curl -sS -i --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/invoice.png;type=image/png" \
-F "file=@$WORK_DIR/invoice.pdf;type=application/pdf"
本文接口一次只接受一个非空文件,因此三种请求都应整体失败,不能由框架静默选择第一个或最后一个文件。若业务支持批量上传,则需要单独定义部分失败、事务边界和返回结构,不能沿用单文件接口的预期。
图片还要准备损坏文件、超大像素尺寸和正常体积但解压后占用很大的样本。PDF 则关注加密文件、损坏交叉引用表、嵌入附件、JavaScript 动作和解析超时。此类样本应来自内部安全样本库,在隔离测试环境使用;不要从未知网站下载后直接放进办公网络。
大小限制要穿过网关和应用两层
如果网关限制 8 MiB,业务接口限制 10 MiB,那么 9 MiB 文件永远到不了业务服务。网关通常限制的是包含 multipart boundary 和字段头的整个请求体,若网关与文件上限都配置为 10 MiB,恰好 10 MiB 的文件也可能被网关提前拒绝。测试前先核对各层配置,再发送前面准备的三份合法 PDF。
bash
CLAIM_ID='claim-upload-size'
upload_size_case() {
LABEL="$1"
PDF_FILE="$2"
HTTP_CODE=$(curl -sS --config "$CURL_AUTH_CONFIG" \
-D "$WORK_DIR/size-$LABEL.headers" \
-o "$WORK_DIR/size-$LABEL.json" \
-w '%{http_code}' \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$PDF_FILE;type=application/pdf")
}
upload_size_case under "$WORK_DIR/invoice-under-limit.pdf"
test "$HTTP_CODE" = '201'
upload_size_case exact "$WORK_DIR/invoice-at-limit.pdf"
test "$HTTP_CODE" = '201'
upload_size_case over "$WORK_DIR/invoice-over-limit.pdf"
test "$HTTP_CODE" = '413'
grep -qi '^content-type: application/json' "$WORK_DIR/size-over.headers"
jq -e '.code == "FILE_TOO_LARGE"' "$WORK_DIR/size-over.json" >/dev/null
本文契约要求小于和等于 10 MiB 的合法文件成功,大于 1 字节的文件返回 413 和 FILE_TOO_LARGE。响应头和响应体都被保留,因此可以区分网关 HTML 错误与业务 JSON;如果网关负责拒绝,也应按团队约定转换成同一错误结构,不能返回 HTML 502 或让客户端一直停在 99%。服务端还要在读取完整请求体之前尽早拒绝明显超限的上传,避免大文件占满内存和临时磁盘。
还要观察失败后的资源:临时目录没有残留文件,对象存储没有未关联分片,数据库没有永久停留在 UPLOADING 的记录。同一测试在直连业务服务和经过网关两条路径各跑一次,能快速定位限制来自哪一层。
文件名测试要看存储名和下载响应
文件名至少覆盖空扩展名、多个点、前后空格、中文、emoji、超长名称和路径字符。真正危险的不是页面显示难看,而是服务端把用户文件名直接拼到磁盘路径或对象 Key 中。
发送 multipart 时可以用 filename= 覆盖上传文件名:
bash
CLAIM_ID='claim-upload-filename'
curl -sS -i --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/invoice.png;filename=../../outside.png;type=image/png"
curl -sS -i --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/invoice.png;filename=报销单%0d%0aX-Test:1.png;type=image/png"
第二个请求发送的是 CRLF 的百分号编码序列,用来发现服务端是否会先解码再拼接响应头;验证原始 CRLF 字节时,应使用安全测试代理构造 multipart 请求并保留原始报文。服务端可以拒绝危险名称,也可以安全规范化后保存,但对象 Key 必须由服务端生成,不能出现 ../、反斜杠或用户控制的目录。下载响应中的 Content-Disposition 也要检查,文件名需要正确编码,不能借 CRLF 注入额外响应头。
同名上传要连续执行两次。本文契约允许共存,因此应生成两个附件 ID 和两个对象 Key,下载内容分别对应各自摘要;如果项目采用覆盖策略,则必须验证权限、版本记录和并发覆盖规则,不能只看列表中"还剩一条"。
第 6 个附件要检查原子性
使用独立报销单先上传 4 个合法附件,确认基线数量后再并发提交两个新文件:
bash
CLAIM_ID='claim-upload-limit'
for number in 1 2 3 4; do
curl -sS --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/invoice.png;filename=existing-$number.png;type=image/png" \
-o "$WORK_DIR/existing-$number.json"
done
BASELINE_COUNT=$(curl -sS --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" | jq -er '.items | length')
test "$BASELINE_COUNT" = '4'
for name in a b; do
curl -sS --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/invoice.png;filename=$name.png;type=image/png" \
-o "$WORK_DIR/limit-$name.json" \
-w '%{http_code}' > "$WORK_DIR/limit-$name.status" &
done
wait
SUCCESS_COUNT=$(grep -l '^201$' "$WORK_DIR"/limit-*.status | wc -l | tr -d ' ')
LIMIT_COUNT=$(jq -s '[.[] | select(.code == "ATTACHMENT_LIMIT_EXCEEDED")] | length' \
"$WORK_DIR"/limit-*.json)
FINAL_COUNT=$(curl -sS --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" | jq -er '.items | length')
test "$SUCCESS_COUNT" = '1'
test "$LIMIT_COUNT" = '1'
test "$FINAL_COUNT" = '5'
最终只能新增一个附件,总数为 5;另一个请求返回 ATTACHMENT_LIMIT_EXCEEDED。数据库不能生成两条有效记录,对象存储也不能留下失败请求的孤儿对象。连续执行一次"删除 1 个附件再上传",确认计数释放时机与对象异步清理互不干扰。
如果客户端会自动重试,再为上传请求增加幂等键。制造"服务端已经保存,但响应在网关处超时"的场景,然后用同一幂等键重试;最终只应有一个附件 ID 和一个有效对象。换一个幂等键再次上传同一文件,则按同名策略处理,不能把内容摘要误当成全局幂等键。
故障注入要覆盖跨系统部分成功
参数校验失败通常发生在写存储之前,真正容易留下脏数据的是某一步已经成功、下一步失败。测试环境可以使用受权限控制的 Failpoint、故障代理或暂停依赖服务来制造下面四个断点,不能把故障开关注入生产接口:
| 故障位置 | 注入动作 | 通过条件 |
|---|---|---|
| 对象写入后、元数据提交前 | 让数据库提交失败 | 接口失败,对象被补偿删除,不存在附件记录 |
| 元数据提交后、扫描事件发送前 | 暂停消息服务或让发布失败 | 记录不会永久停在 SCANNING,恢复后只补发一次有效任务 |
| 扫描完成后、状态回写前 | 让状态更新失败 | 重试后得到一个最终状态,扫描结果不会被旧状态覆盖 |
| 元数据删除后、对象删除前 | 让对象存储删除失败 | 用户不可再下载,清理任务最终删除孤儿对象 |
每个断点都使用新的报销单和文件摘要,记录附件 ID、对象 Key、任务 ID 和故障时间。恢复依赖后轮询到约定的最终状态,再查询对象存储和任务表;只看接口当时返回 500,无法证明补偿已经完成。对于采用 Outbox、事务消息或定时对账的系统,还要检查重复执行不会创建第二个对象、第二条扫描任务或把已拒绝文件重新发布。
中断、分片和断点续传要查未完成状态
普通 multipart 上传可以在发送过程中断网或终止客户端,观察服务端是否释放临时文件和连接。测试时不要只关闭页面,因为浏览器可能已经把请求体发送完。下面使用合法的 10 MiB PDF,将上传限速为 100 KiB/s,并在 2 秒后由客户端主动中止:
bash
CLAIM_ID='claim-upload-interrupt'
set +e
curl -sS --config "$CURL_AUTH_CONFIG" \
--limit-rate 100K --max-time 2 \
"$API_URL/api/claims/$CLAIM_ID/attachments" \
-F "file=@$WORK_DIR/invoice-at-limit.pdf;filename=interrupted.pdf;type=application/pdf" \
-o "$WORK_DIR/interrupted.json"
CURL_RC=$?
set -e
test "$CURL_RC" = '28'
INTERRUPTED_COUNT=$(curl -sS --config "$CURL_AUTH_CONFIG" \
"$API_URL/api/claims/$CLAIM_ID/attachments" |
jq -er '[.items[] | select(.originalName == "interrupted.pdf")] | length')
test "$INTERRUPTED_COUNT" = '0'
退出码 28 表示这次请求确实因超时中止,列表接口也没有可见附件。随后仍要从服务端日志确认连接实际中断和已接收字节数,再检查临时文件、元数据和对象存储;其他退出码应先排查 DNS、TLS、鉴权或连接错误,不能记为中断上传通过。
如果系统使用分片上传,需要先明确 uploadId、分片编号、分片摘要、合并和取消接口。至少执行这些动作:
- 缺少中间分片后请求合并,必须失败且不能生成可下载文件;
- 重复上传同一分片,按契约覆盖或幂等处理,不能重复计入总大小;
- 交换分片编号,合并后的摘要必须不匹配并失败;
- 两个用户交换
uploadId,服务端必须拒绝; - 合并请求并发执行两次,只生成一个最终对象;
- 取消或超时后继续上传旧分片,不能恢复已失效任务。
每次失败都要检查分片生命周期。未完成分片应在约定时间后清理,已合并分片不应继续占用双份空间,合并失败也不能留下一个能被下载的半成品。
扫描期间和扫描失败都不能直接下载
上传响应返回 SCANNING 后,立即请求下载接口,预期是 423、409 或项目约定的不可用错误,而不是提前返回原文件。扫描通过后状态变为 AVAILABLE,扫描不通过则变为 REJECTED,并删除或隔离对象。
病毒扫描使用安全团队批准的测试样本,例如 EICAR 测试文件,不要自行制作或传播恶意程序。消息系统可能重复投递扫描任务,因此要验证处理幂等:同一附件即使被扫描多次,也只能得到一个最终状态,不能重复进入 OCR、预览和分享链路。用户应收到明确提示,安全审计记录包含附件 ID 和命中规则但不包含文件原文。
扫描服务超时时,系统应进入明确的重试或失败状态。不要把超时默认当作"扫描通过",也不要无限停留在 SCANNING。可以暂停测试扫描服务,记录重试次数、最终状态和临时对象清理时间,再恢复服务验证补偿任务是否会重复发布附件。
下载接口继续验证权限和响应头
上传成功不代表下载安全。使用以下身份请求同一个附件:上传者、同项目其他成员、无项目权限用户、另一个租户用户和未登录用户。只有契约允许的身份可以拿到内容;越权请求失败后,不能通过对象存储直链绕过业务鉴权。
下载响应至少检查:
Content-Type与服务端识别结果一致,不盲信上传请求值;Content-Disposition的文件名可正确显示,且不能注入响应头;- 对可能被浏览器解释的内容设置合适的下载方式和
X-Content-Type-Options: nosniff; - 私有对象的签名 URL 有短有效期,并绑定正确对象和操作;
- 删除、驳回或权限回收后,旧下载地址按契约失效。
签名 URL 不要写入普通访问日志、埋点和缺陷截图。验证过期时应使用测试环境可配置的短有效期,避免长时间等待,也不要通过修改本机时间代替服务端过期判断。
回归时保留这些场景
| 场景 | 执行动作 | 关键证据 |
|---|---|---|
| 正常上传 | 上传合法 PNG 并下载 | 状态流转、对象长度、上传前后摘要一致 |
| 格式伪装 | 文本内容命名为 .jpg |
拒绝上传,无对象、无后续任务 |
| 大小边界 | 上传上限值和上限加 1 字节文件 | 临界值结果、网关与应用错误来源 |
| 危险文件名 | 使用路径和 CRLF 编码序列作为文件名 | 对象 Key 安全,响应头未被注入 |
| 同名文件 | 连续上传两份同名不同内容文件 | 结果符合共存或覆盖契约 |
| 数量并发 | 已有 4 个附件时并发上传 2 个 | 最终总数为 5,无孤儿对象 |
| 响应丢失重试 | 保存成功后让响应超时,再用同一幂等键重试 | 只有一个附件和对象 |
| 部分成功 | 在存储、数据库和消息之间注入失败 | 补偿完成,无永久中间状态和重复任务 |
| 分片异常 | 缺片、乱序或并发合并 | 合并失败或幂等成功,无半成品下载 |
| 扫描失败 | 上传批准的安全测试样本 | 状态为 REJECTED,不进入预览和分享链路 |
| 下载越权 | 跨项目、跨租户和未登录下载 | 请求被拒绝,存储直链不可绕过 |
这些场景适合作为上传模块的主回归集。格式列表、大小限制、附件数量或存储链路发生变化时,再按变更点扩展测试,不需要每次机械重跑所有低风险文件名组合。
总结
文件上传测试需要把一个文件从请求体追到最终下载:请求是否被正确解析,元数据和对象是否一致,异常请求有没有留下副作用,扫描前后的状态是否可控,下载时权限和响应头是否仍然安全。扩展名、大小和页面提示只是入口,真正能定位问题的是文件摘要、对象 Key、状态流转、任务记录和下载结果组成的证据链。
先用脚本固定测试文件,再围绕正常上传、边界、并发、中断和安全场景复用同一批数据,回归会比临时找几个附件点击上传稳定得多。