feat: update app workflows and permissions
This commit is contained in:
@ -233,6 +233,17 @@ final class WildPhotographerReportTests: XCTestCase {
|
||||
"handle_status": 0,
|
||||
"handle_status_text": "待处理",
|
||||
"handle_remark": "",
|
||||
"handled_at": "2026-07-09 14:18:40",
|
||||
"handler": {
|
||||
"id": 43,
|
||||
"name": "管铜彬",
|
||||
"phone": "132****9413",
|
||||
"started_at": "2026-07-09 14:18:15",
|
||||
"finished_at": "2026-07-09 14:18:40",
|
||||
"remark": "我是处理意见"
|
||||
},
|
||||
"handler_name": "管铜彬",
|
||||
"handler_phone": "132****9413",
|
||||
"evidence_summary": { "image_count": 1, "video_count": 1 },
|
||||
"evidences": [
|
||||
{
|
||||
@ -248,6 +259,24 @@ final class WildPhotographerReportTests: XCTestCase {
|
||||
"file_name": "report.mov"
|
||||
}
|
||||
],
|
||||
"handle_evidences": [
|
||||
{
|
||||
"evidence_kind": 2,
|
||||
"evidence_kind_text": "处理凭证",
|
||||
"file_type": 1,
|
||||
"file_type_text": "图片",
|
||||
"file_url": "http://tmp/handler-one.jpg",
|
||||
"file_name": "handler-one.jpg"
|
||||
},
|
||||
{
|
||||
"evidence_kind": 2,
|
||||
"evidence_kind_text": "处理凭证",
|
||||
"file_type": 1,
|
||||
"file_type_text": "图片",
|
||||
"file_url": "http://tmp/handler-two.png",
|
||||
"file_name": "handler-two.png"
|
||||
}
|
||||
],
|
||||
"payment_screenshots": [
|
||||
{
|
||||
"file_type": 5,
|
||||
@ -276,6 +305,55 @@ final class WildPhotographerReportTests: XCTestCase {
|
||||
XCTAssertEqual(response.evidences.count, 2)
|
||||
XCTAssertEqual(response.paymentScreenshots.first?.fileName, "payment.jpg")
|
||||
XCTAssertEqual(response.timeline.first?.content, "摄影师提交举报")
|
||||
XCTAssertEqual(response.handler?.name, "管铜彬")
|
||||
XCTAssertEqual(response.handler?.phone, "132****9413")
|
||||
XCTAssertEqual(response.handler?.startedAt, "2026-07-09 14:18:15")
|
||||
XCTAssertEqual(response.handler?.finishedAt, "2026-07-09 14:18:40")
|
||||
XCTAssertEqual(response.handler?.remark, "我是处理意见")
|
||||
XCTAssertEqual(response.handledAt, "2026-07-09 14:18:40")
|
||||
XCTAssertEqual(response.handleEvidences.count, 2)
|
||||
XCTAssertEqual(response.handleEvidences.first?.evidenceKind, 2)
|
||||
XCTAssertEqual(response.handleEvidences.first?.evidenceKindText, "处理凭证")
|
||||
XCTAssertEqual(response.handleEvidences.first?.fileURL, "http://tmp/handler-one.jpg")
|
||||
XCTAssertEqual(response.handleEvidences.last?.fileName, "handler-two.png")
|
||||
}
|
||||
|
||||
func testReportShareAPILoadsMiniProgramConfig() async throws {
|
||||
let json = Data("""
|
||||
{
|
||||
"code": 100000,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"userName": "wx8c85189bf3bdda29",
|
||||
"path": "pages/scenic/radar/report-detail/index?id=32",
|
||||
"title": "私下收款举报",
|
||||
"imagePath": "/assets/share/report-share-cover.png",
|
||||
"webpageUrl": "https://www.youfuntour.com",
|
||||
"withShareTicket": true,
|
||||
"miniprogramType": 0,
|
||||
"scene": 0
|
||||
},
|
||||
"time": "2026-07-09 15:42:19"
|
||||
}
|
||||
""".utf8)
|
||||
let session = MockURLSession(responses: [json])
|
||||
let api = WildPhotographerReportAPI(client: APIClient(environment: .testing, session: session))
|
||||
|
||||
let response = try await api.reportShare(id: "32")
|
||||
|
||||
let components = URLComponents(url: try XCTUnwrap(session.requests.first?.url), resolvingAgainstBaseURL: false)
|
||||
XCTAssertEqual(session.requests.first?.url?.path, "/api/yf-handset-app/photog/report/share")
|
||||
XCTAssertTrue(components?.queryItems?.contains(URLQueryItem(name: "id", value: "32")) == true)
|
||||
XCTAssertEqual(response.userName, "wx8c85189bf3bdda29")
|
||||
XCTAssertEqual(response.path, "pages/scenic/radar/report-detail/index?id=32")
|
||||
XCTAssertEqual(response.title, "私下收款举报")
|
||||
XCTAssertEqual(response.imagePath, "/assets/share/report-share-cover.png")
|
||||
XCTAssertEqual(response.webpageURL, "https://www.youfuntour.com")
|
||||
XCTAssertTrue(response.withShareTicket)
|
||||
XCTAssertEqual(response.miniProgramType, .release)
|
||||
XCTAssertEqual(response.scene, .session)
|
||||
XCTAssertEqual(response.payload.userName, "wx8c85189bf3bdda29")
|
||||
XCTAssertEqual(response.payload.path, "pages/scenic/radar/report-detail/index?id=32")
|
||||
}
|
||||
|
||||
func testSupplementReportAPISendsRequestBody() async throws {
|
||||
@ -472,6 +550,106 @@ final class WildPhotographerReportTests: XCTestCase {
|
||||
XCTAssertEqual(detail.supplementRecords.first?.summaryText, "补充说明 · 1张图片")
|
||||
}
|
||||
|
||||
func testSupplementRecordSequenceTitleStartsFromDisplayOrder() {
|
||||
let titles = (0..<3).map {
|
||||
WildReportSupplementDisplayFormatter.sequenceTitle(forDisplayIndex: $0)
|
||||
}
|
||||
|
||||
XCTAssertEqual(titles, ["第1次补充", "第2次补充", "第3次补充"])
|
||||
}
|
||||
|
||||
func testReportDetailMapsHandlerInfoFromHandlerFields() throws {
|
||||
let detail = makeReportDetailData(
|
||||
handleStatus: 2,
|
||||
handleStatusText: "已处理",
|
||||
handleRemark: "顶层处理备注",
|
||||
handledAt: "2026-07-09 14:18:40",
|
||||
handler: WildReportDetailHandler(
|
||||
id: 43,
|
||||
name: "管铜彬",
|
||||
phone: "132****9413",
|
||||
startedAt: "2026-07-09 14:18:15",
|
||||
finishedAt: "2026-07-09 14:18:40",
|
||||
remark: "我是处理意见"
|
||||
),
|
||||
handleEvidences: [
|
||||
WildReportDetailEvidence(
|
||||
fileType: 1,
|
||||
fileTypeText: "图片",
|
||||
fileURL: "http://tmp/handler-one.jpg",
|
||||
fileName: "handler-one.jpg",
|
||||
evidenceKind: 2,
|
||||
evidenceKindText: "处理凭证"
|
||||
),
|
||||
WildReportDetailEvidence(
|
||||
fileType: 1,
|
||||
fileTypeText: "图片",
|
||||
fileURL: "http://tmp/handler-two.png",
|
||||
fileName: "handler-two.png",
|
||||
evidenceKind: 2,
|
||||
evidenceKindText: "处理凭证"
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
let info = try XCTUnwrap(detail.toRecord().handlerInfo)
|
||||
|
||||
XCTAssertEqual(info.handlerName, "管铜彬")
|
||||
XCTAssertEqual(info.handlerPhone, "132****9413")
|
||||
XCTAssertEqual(info.processingAt, "2026-07-09 14:18:15")
|
||||
XCTAssertEqual(info.processedAt, "2026-07-09 14:18:40")
|
||||
XCTAssertEqual(info.processOpinion, "我是处理意见")
|
||||
XCTAssertEqual(info.completionMedia.count, 2)
|
||||
XCTAssertEqual(info.completionMedia.first?.coverURL, "http://tmp/handler-one.jpg")
|
||||
XCTAssertEqual(info.completionMediaSummary, "2张")
|
||||
}
|
||||
|
||||
func testReportDetailMapsHandlerInfoFromFallbackFields() throws {
|
||||
let detail = makeReportDetailData(
|
||||
handleStatus: 2,
|
||||
handleStatusText: "已处理",
|
||||
handleRemark: "fallback 处理意见",
|
||||
handledAt: "2026-07-09 14:18:40",
|
||||
handlerName: "管铜彬",
|
||||
handlerPhone: "132****9413"
|
||||
)
|
||||
|
||||
let info = try XCTUnwrap(detail.toRecord().handlerInfo)
|
||||
|
||||
XCTAssertEqual(info.handlerName, "管铜彬")
|
||||
XCTAssertEqual(info.handlerPhone, "132****9413")
|
||||
XCTAssertEqual(info.processingAt, "")
|
||||
XCTAssertEqual(info.processedAt, "2026-07-09 14:18:40")
|
||||
XCTAssertEqual(info.processOpinion, "fallback 处理意见")
|
||||
}
|
||||
|
||||
func testReportShareDataDecodesSnakeCaseAndMapsPayload() throws {
|
||||
let json = Data("""
|
||||
{
|
||||
"user_name": "gh_report_original_id",
|
||||
"path": "pages/report/detail?id=32",
|
||||
"title": "私下收款举报",
|
||||
"description": "查看举报处理进度",
|
||||
"image_path": "https://example.com/share.png",
|
||||
"webpage_url": "https://www.youfuntour.com/report/32",
|
||||
"with_share_ticket": true,
|
||||
"mini_program_type": 2,
|
||||
"scene": 0,
|
||||
"disable_forward": true
|
||||
}
|
||||
""".utf8)
|
||||
|
||||
let data = try JSONDecoder().decode(WildReportMiniProgramShareData.self, from: json)
|
||||
|
||||
XCTAssertEqual(data.userName, "gh_report_original_id")
|
||||
XCTAssertEqual(data.webpageURL, "https://www.youfuntour.com/report/32")
|
||||
XCTAssertEqual(data.miniProgramType, .preview)
|
||||
XCTAssertEqual(data.scene, .session)
|
||||
XCTAssertEqual(data.payload.description, "查看举报处理进度")
|
||||
XCTAssertTrue(data.payload.withShareTicket)
|
||||
XCTAssertTrue(data.payload.disableForward)
|
||||
}
|
||||
|
||||
func testReportStatusMapsRejected() {
|
||||
XCTAssertEqual(WildReportStatus(handleStatus: 0), .pending)
|
||||
XCTAssertEqual(WildReportStatus(handleStatus: 1), .processing)
|
||||
@ -731,6 +909,61 @@ final class WildPhotographerReportTests: XCTestCase {
|
||||
XCTAssertEqual(messages.last, URLError(.badServerResponse).localizedDescription)
|
||||
}
|
||||
|
||||
func testListViewModelLoadsMiniProgramSharePayload() async throws {
|
||||
let homeViewModel = WildPhotographerReportHomeViewModel()
|
||||
let viewModel = WildPhotographerReportListViewModel(homeViewModel: homeViewModel)
|
||||
let record = makeReportListItem(complaintNo: "JB20260708917", handleStatus: 0).toRecord()
|
||||
let api = MockWildPhotographerReportAPI()
|
||||
api.reportShareResponse = WildReportMiniProgramShareData(
|
||||
userName: "gh_report_original_id",
|
||||
path: "pages/report/detail?id=32",
|
||||
title: "私下收款举报",
|
||||
description: "查看举报处理进度",
|
||||
webpageURL: "https://www.youfuntour.com/report/32",
|
||||
withShareTicket: true,
|
||||
miniProgramTypeValue: WeChatMiniProgramType.preview.rawValue
|
||||
)
|
||||
|
||||
let maybePayload = await viewModel.shareReport(record, api: api)
|
||||
let payload = try XCTUnwrap(maybePayload)
|
||||
|
||||
XCTAssertEqual(api.reportShareRequests, ["32"])
|
||||
XCTAssertEqual(payload.title, "私下收款举报")
|
||||
XCTAssertEqual(payload.userName, "gh_report_original_id")
|
||||
XCTAssertEqual(payload.path, "pages/report/detail?id=32")
|
||||
XCTAssertEqual(payload.miniProgramType, .preview)
|
||||
XCTAssertTrue(payload.withShareTicket)
|
||||
}
|
||||
|
||||
func testListViewModelShareRejectsLocalRecord() async {
|
||||
let homeViewModel = WildPhotographerReportHomeViewModel()
|
||||
let viewModel = WildPhotographerReportListViewModel(homeViewModel: homeViewModel)
|
||||
let record = WildReportRecord(
|
||||
serverID: nil,
|
||||
id: "JBLOCAL",
|
||||
title: "摄影师举报",
|
||||
reportType: WildReportType(value: 2, label: "疑似打野"),
|
||||
status: .pending,
|
||||
location: "测试景区",
|
||||
submitTime: "2026-07-08 16:06:59",
|
||||
description: "本地记录",
|
||||
wechatID: "",
|
||||
phoneNumber: "",
|
||||
imageCount: 1,
|
||||
videoCount: 0,
|
||||
handlerInfo: nil
|
||||
)
|
||||
let api = MockWildPhotographerReportAPI()
|
||||
var messages: [String] = []
|
||||
viewModel.onShowMessage = { messages.append($0) }
|
||||
|
||||
let payload = await viewModel.shareReport(record, api: api)
|
||||
|
||||
XCTAssertNil(payload)
|
||||
XCTAssertTrue(api.reportShareRequests.isEmpty)
|
||||
XCTAssertEqual(messages.last, "当前举报暂未同步到服务器,无法分享")
|
||||
}
|
||||
|
||||
func testSupplementEvidenceValidationAndSuccess() async throws {
|
||||
let homeViewModel = WildPhotographerReportHomeViewModel()
|
||||
let record = makeReportListItem(complaintNo: "JB20260708917", handleStatus: 0).toRecord()
|
||||
@ -1023,7 +1256,28 @@ final class WildPhotographerReportTests: XCTestCase {
|
||||
let record = makeReportListItem(complaintNo: "JB20260708917", handleStatus: 0).toRecord()
|
||||
let viewModel = WildPhotographerReportDetailViewModel(record: record)
|
||||
let api = MockWildPhotographerReportAPI()
|
||||
api.reportDetailResponses = [makeReportDetailData()]
|
||||
api.reportDetailResponses = [
|
||||
makeReportDetailData(
|
||||
handleStatus: 2,
|
||||
handleStatusText: "已处理",
|
||||
handler: WildReportDetailHandler(
|
||||
id: 43,
|
||||
name: "管铜彬",
|
||||
phone: "132****9413",
|
||||
startedAt: "2026-07-09 14:18:15",
|
||||
finishedAt: "2026-07-09 14:18:40",
|
||||
remark: "我是处理意见"
|
||||
),
|
||||
handleEvidences: [
|
||||
WildReportDetailEvidence(
|
||||
fileType: 1,
|
||||
fileTypeText: "图片",
|
||||
fileURL: "http://tmp/handler-one.jpg",
|
||||
fileName: "handler-one.jpg"
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
await viewModel.loadDetailIfNeeded(api: api)
|
||||
|
||||
@ -1032,6 +1286,10 @@ final class WildPhotographerReportTests: XCTestCase {
|
||||
XCTAssertEqual(viewModel.reporterMediaItems.count, 2)
|
||||
XCTAssertEqual(viewModel.paymentScreenshotItems.count, 1)
|
||||
XCTAssertEqual(viewModel.progressSteps.first?.title, "摄影师提交举报")
|
||||
XCTAssertTrue(viewModel.showHandlerInfo)
|
||||
XCTAssertEqual(viewModel.handlerInfo?.handlerName, "管铜彬")
|
||||
XCTAssertEqual(viewModel.handlerInfo?.processingAt, "2026-07-09 14:18:15")
|
||||
XCTAssertEqual(viewModel.handlerInfo?.completionMedia.first?.coverURL, "http://tmp/handler-one.jpg")
|
||||
}
|
||||
|
||||
func testDetailViewModelFailureKeepsInitialRecord() async {
|
||||
@ -1048,6 +1306,27 @@ final class WildPhotographerReportTests: XCTestCase {
|
||||
XCTAssertEqual(messages.last, URLError(.badServerResponse).localizedDescription)
|
||||
}
|
||||
|
||||
func testDetailViewModelLoadsMiniProgramSharePayload() async throws {
|
||||
let record = makeReportListItem(complaintNo: "JB20260708917", handleStatus: 0).toRecord()
|
||||
let viewModel = WildPhotographerReportDetailViewModel(record: record)
|
||||
let api = MockWildPhotographerReportAPI()
|
||||
api.reportShareResponse = WildReportMiniProgramShareData(
|
||||
userName: "gh_report_original_id",
|
||||
path: "pages/report/detail?id=32",
|
||||
title: "私下收款举报",
|
||||
webpageURL: "https://www.youfuntour.com/report/32",
|
||||
miniProgramTypeValue: WeChatMiniProgramType.test.rawValue
|
||||
)
|
||||
|
||||
let maybePayload = await viewModel.shareReport(api: api)
|
||||
let payload = try XCTUnwrap(maybePayload)
|
||||
|
||||
XCTAssertEqual(api.reportShareRequests, ["32"])
|
||||
XCTAssertEqual(payload.webpageURL, "https://www.youfuntour.com/report/32")
|
||||
XCTAssertEqual(payload.userName, "gh_report_original_id")
|
||||
XCTAssertEqual(payload.miniProgramType, .test)
|
||||
}
|
||||
|
||||
func testDetailViewModelWithoutServerIDSkipsRequest() async {
|
||||
let record = WildReportRecord(
|
||||
id: "JBLOCAL",
|
||||
@ -1087,6 +1366,9 @@ private final class MockWildPhotographerReportAPI: WildPhotographerReportServing
|
||||
var reportDetailResponses: [WildReportDetailData] = []
|
||||
var reportDetailRequests: [Int] = []
|
||||
var reportDetailError: Error?
|
||||
var reportShareResponse = WildReportMiniProgramShareData()
|
||||
var reportShareRequests: [String] = []
|
||||
var reportShareError: Error?
|
||||
var supplementRequests: [WildReportSupplementRequest] = []
|
||||
var supplementError: Error?
|
||||
var riskMapResponses: [WildReportRiskMapResponse] = []
|
||||
@ -1150,6 +1432,12 @@ private final class MockWildPhotographerReportAPI: WildPhotographerReportServing
|
||||
guard !reportDetailResponses.isEmpty else { return WildReportDetailData(id: id) }
|
||||
return reportDetailResponses.removeFirst()
|
||||
}
|
||||
|
||||
func reportShare(id: String) async throws -> WildReportMiniProgramShareData {
|
||||
if let reportShareError { throw reportShareError }
|
||||
reportShareRequests.append(id)
|
||||
return reportShareResponse
|
||||
}
|
||||
}
|
||||
|
||||
private enum MockRiskMapLocationResult: Sendable {
|
||||
@ -1312,7 +1600,13 @@ private func makeReportListItem(
|
||||
|
||||
private func makeReportDetailData(
|
||||
handleStatus: Int = 0,
|
||||
handleStatusText: String = "待处理"
|
||||
handleStatusText: String = "待处理",
|
||||
handleRemark: String = "处理备注",
|
||||
handledAt: String = "",
|
||||
handler: WildReportDetailHandler? = nil,
|
||||
handlerName: String = "",
|
||||
handlerPhone: String = "",
|
||||
handleEvidences: [WildReportDetailEvidence] = []
|
||||
) -> WildReportDetailData {
|
||||
WildReportDetailData(
|
||||
id: 32,
|
||||
@ -1349,7 +1643,11 @@ private func makeReportDetailData(
|
||||
longitude: "119.4403263",
|
||||
handleStatus: handleStatus,
|
||||
handleStatusText: handleStatusText,
|
||||
handleRemark: "处理备注",
|
||||
handleRemark: handleRemark,
|
||||
handledAt: handledAt,
|
||||
handler: handler,
|
||||
handlerName: handlerName,
|
||||
handlerPhone: handlerPhone,
|
||||
evidenceSummary: WildReportEvidenceSummary(imageCount: 1, videoCount: 1),
|
||||
evidences: [
|
||||
WildReportDetailEvidence(
|
||||
@ -1365,6 +1663,7 @@ private func makeReportDetailData(
|
||||
fileName: "report.mov"
|
||||
)
|
||||
],
|
||||
handleEvidences: handleEvidences,
|
||||
paymentScreenshots: [
|
||||
WildReportDetailEvidence(
|
||||
fileType: 5,
|
||||
|
||||
Reference in New Issue
Block a user