docs(private): 完善私有化部署开发计划和设计规范
- 增加实时进度和交接规则,定义任务状态枚举和更新格式 - 创建任务进度台账,涵盖P0-P5阶段全部开发任务 - 补充部署仓库交付边界确认和进度审计规范 - 完善MySQL/Redis双模式支持,增加external/managed选项 - 增加离线部署、安全治理、可观测性等完整交付能力 - 更新仓库结构设计,增加secrets.env、observability、data目录 - 补充健康检查、诊断脚本、升级回滚、备份恢复详细要求 - 优化应用商店审核状态查询逻辑,增加手动刷新接口 - 修复小米和VIVO商店状态查询中的版本匹配逻辑错误 - 增加缓存键版本隔离,防止不同版本状态混淆 - 优化厂商API连通性检查和审核状态轮询机制
这个提交包含在:
父节点
87edb316a5
当前提交
93fdb31cdc
@ -194,6 +194,20 @@ public class AppStoreController {
|
|||||||
return ResponseEntity.ok(ApiResponse.success(null));
|
return ResponseEntity.ok(ApiResponse.success(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually refresh the review status for all target stores of a version.
|
||||||
|
* Queries each store's remote API and updates the local state if it has changed.
|
||||||
|
*/
|
||||||
|
@PostMapping("/app/{versionId}/refresh-review-status")
|
||||||
|
public ResponseEntity<ApiResponse<com.xuqm.update.model.PreflightSubmitResult>> refreshReviewStatus(
|
||||||
|
@PathVariable String versionId) {
|
||||||
|
AppVersionEntity v = submissionService.getVersionForPreflight(versionId);
|
||||||
|
List<com.xuqm.update.model.StoreRemoteState> states = submissionService.refreshStoreReviewStatus(versionId);
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(
|
||||||
|
com.xuqm.update.model.PreflightSubmitResult.of(
|
||||||
|
versionId, v.getVersionName(), v.getVersionCode(), states)));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Modify publish schedule ──────────────────────────────────────────────
|
// ── Modify publish schedule ──────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -644,6 +644,11 @@ public class AppStoreService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (entry instanceof Map<?, ?> map) {
|
if (entry instanceof Map<?, ?> map) {
|
||||||
|
// Exclude non-current releases from auto-publish
|
||||||
|
Object nonCurrentRelease = map.get("nonCurrentRelease");
|
||||||
|
if (nonCurrentRelease instanceof Boolean b && b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Object currentSubmissionLive = map.get("currentSubmissionLive");
|
Object currentSubmissionLive = map.get("currentSubmissionLive");
|
||||||
return !(currentSubmissionLive instanceof Boolean b) || b;
|
return !(currentSubmissionLive instanceof Boolean b) || b;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -519,21 +519,29 @@ public class StoreSubmissionService {
|
|||||||
int appStatus = pkg.path("appStatus").asInt(pkg.path("status").asInt(-1));
|
int appStatus = pkg.path("appStatus").asInt(pkg.path("status").asInt(-1));
|
||||||
boolean updateVersion = root.path("updateVersion").asBoolean(false);
|
boolean updateVersion = root.path("updateVersion").asBoolean(false);
|
||||||
String submittedCode = String.valueOf(v.getVersionCode());
|
String submittedCode = String.valueOf(v.getVersionCode());
|
||||||
boolean isLive = updateVersion || submittedCode.equals(onlineVersionCode);
|
// updateVersion=true means "there is a live version" but NOT necessarily the current submission.
|
||||||
|
// Only treat as live for the CURRENT submission when onlineVersionCode matches submittedCode.
|
||||||
|
boolean currentSubmissionLive = submittedCode.equals(onlineVersionCode) && !submittedCode.isBlank();
|
||||||
|
boolean hasOnlineVersion = updateVersion || !onlineVersionCode.isBlank();
|
||||||
|
boolean nonCurrentRelease = hasOnlineVersion && !currentSubmissionLive;
|
||||||
StoreRemoteState.ReviewState reviewState;
|
StoreRemoteState.ReviewState reviewState;
|
||||||
if (appStatus == 5) {
|
if (appStatus == 5) {
|
||||||
reviewState = StoreRemoteState.ReviewState.REJECTED;
|
reviewState = StoreRemoteState.ReviewState.REJECTED;
|
||||||
} else if (updateVersion) {
|
} else if (currentSubmissionLive) {
|
||||||
reviewState = StoreRemoteState.ReviewState.ONLINE;
|
reviewState = StoreRemoteState.ReviewState.ONLINE;
|
||||||
} else {
|
} else {
|
||||||
|
// Keep UNDER_REVIEW when no explicit rejection for current version.
|
||||||
|
// updateVersion alone is unreliable for version attribution.
|
||||||
reviewState = StoreRemoteState.ReviewState.UNDER_REVIEW_XIAOMI;
|
reviewState = StoreRemoteState.ReviewState.UNDER_REVIEW_XIAOMI;
|
||||||
}
|
}
|
||||||
|
log.info("MI remote state queried: versionId={} submittedCode={} onlineVersionCode={} onlineVersionName={} appStatus={} updateVersion={} reviewState={} currentSubmissionLive={} nonCurrentRelease={}",
|
||||||
|
v.getId(), submittedCode, onlineVersionCode, onlineVersionName, appStatus, updateVersion, reviewState, currentSubmissionLive, nonCurrentRelease);
|
||||||
return StoreRemoteState.ok(
|
return StoreRemoteState.ok(
|
||||||
AppStoreConfigEntity.StoreType.MI,
|
AppStoreConfigEntity.StoreType.MI,
|
||||||
reviewState,
|
reviewState,
|
||||||
onlineVersionName, onlineVersionCode,
|
onlineVersionName, onlineVersionCode,
|
||||||
"", "",
|
"", "",
|
||||||
isLive, !isLive && !onlineVersionCode.isBlank(),
|
currentSubmissionLive, nonCurrentRelease,
|
||||||
true, "", sanitizeJson(root));
|
true, "", sanitizeJson(root));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,7 +577,9 @@ public class StoreSubmissionService {
|
|||||||
String accessKey = require(creds, "accessKey", "VIVO");
|
String accessKey = require(creds, "accessKey", "VIVO");
|
||||||
String accessSecret = require(creds, "accessSecret", "VIVO");
|
String accessSecret = require(creds, "accessSecret", "VIVO");
|
||||||
String pkg = requirePackageName(v);
|
String pkg = requirePackageName(v);
|
||||||
String cacheKey = v.getAppKey() + ":" + pkg;
|
// Cache key includes versionCode because StoreRemoteState contains version-specific
|
||||||
|
// fields (currentSubmissionLive, nonCurrentRelease) that differ per version.
|
||||||
|
String cacheKey = v.getAppKey() + ":" + pkg + ":" + v.getVersionCode();
|
||||||
VivoCacheEntry cached = vivoQueryCache.get(cacheKey);
|
VivoCacheEntry cached = vivoQueryCache.get(cacheKey);
|
||||||
if (cached != null && System.currentTimeMillis() - cached.timestamp < VIVO_CACHE_TTL_MS) {
|
if (cached != null && System.currentTimeMillis() - cached.timestamp < VIVO_CACHE_TTL_MS) {
|
||||||
log.debug("VIVO query cache hit for {}", cacheKey);
|
log.debug("VIVO query cache hit for {}", cacheKey);
|
||||||
@ -596,13 +606,17 @@ public class StoreSubmissionService {
|
|||||||
} else {
|
} else {
|
||||||
reviewState = StoreRemoteState.ReviewState.UNKNOWN;
|
reviewState = StoreRemoteState.ReviewState.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
boolean currentSubmissionLive = isLive && submittedCode.equals(onlineVersionCode);
|
||||||
|
boolean nonCurrentRelease = isLive && !submittedCode.equals(onlineVersionCode);
|
||||||
|
log.info("VIVO remote state queried: versionId={} submittedCode={} onlineVersionCode={} onlineVersionName={} status={} reviewState={} currentSubmissionLive={} nonCurrentRelease={}",
|
||||||
|
v.getId(), submittedCode, onlineVersionCode, onlineVersionName, status, reviewState, currentSubmissionLive, nonCurrentRelease);
|
||||||
StoreRemoteState state = StoreRemoteState.ok(
|
StoreRemoteState state = StoreRemoteState.ok(
|
||||||
AppStoreConfigEntity.StoreType.VIVO,
|
AppStoreConfigEntity.StoreType.VIVO,
|
||||||
reviewState,
|
reviewState,
|
||||||
onlineVersionName, onlineVersionCode,
|
onlineVersionName, onlineVersionCode,
|
||||||
"", "",
|
"", "",
|
||||||
isLive && submittedCode.equals(onlineVersionCode),
|
currentSubmissionLive,
|
||||||
isLive && !submittedCode.equals(onlineVersionCode),
|
nonCurrentRelease,
|
||||||
true, "", sanitizeJson(root));
|
true, "", sanitizeJson(root));
|
||||||
vivoQueryCache.put(cacheKey, new VivoCacheEntry(state, System.currentTimeMillis()));
|
vivoQueryCache.put(cacheKey, new VivoCacheEntry(state, System.currentTimeMillis()));
|
||||||
return state;
|
return state;
|
||||||
@ -701,11 +715,17 @@ public class StoreSubmissionService {
|
|||||||
log.debug("Store review poll: {}/{} still UNDER_REVIEW", v.getId(), storeType);
|
log.debug("Store review poll: {}/{} still UNDER_REVIEW", v.getId(), storeType);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (polled.getReviewState() == StoreRemoteState.ReviewState.ONLINE) {
|
// REJECTED: only transition to APPROVED if the CURRENT submission is now live.
|
||||||
log.info("Store review poll: {}/{} was REJECTED but store has live version currentSubmissionLive={} onlineVersionName={} onlineVersionCode={}",
|
// If a different version is online (nonCurrentRelease), keep REJECTED —
|
||||||
v.getId(), storeType, polled.isCurrentSubmissionLive(), polled.getOnlineVersionName(), polled.getOnlineVersionCode());
|
// the rejection of the current submission is still valid.
|
||||||
storeService.updateStoreReviewLive(v.getId(), storeType, !polled.isCurrentSubmissionLive(),
|
if (polled.getReviewState() == StoreRemoteState.ReviewState.ONLINE && polled.isCurrentSubmissionLive()) {
|
||||||
|
log.info("Store review poll: {}/{} was REJECTED but current submission is now live",
|
||||||
|
v.getId(), storeType);
|
||||||
|
storeService.updateStoreReviewLive(v.getId(), storeType, false,
|
||||||
buildLiveReason(polled), buildExtra(polled));
|
buildLiveReason(polled), buildExtra(polled));
|
||||||
|
} else if (polled.getReviewState() == StoreRemoteState.ReviewState.ONLINE && polled.isNonCurrentRelease()) {
|
||||||
|
log.debug("Store review poll: {}/{} was REJECTED and a different version is online — leaving REJECTED",
|
||||||
|
v.getId(), storeType);
|
||||||
}
|
}
|
||||||
// otherwise leave REJECTED as-is
|
// otherwise leave REJECTED as-is
|
||||||
}
|
}
|
||||||
@ -716,6 +736,102 @@ public class StoreSubmissionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually refresh the review status for all target stores of a given version.
|
||||||
|
* Unlike the scheduled poll, this works for any state (not just UNDER_REVIEW/REJECTED)
|
||||||
|
* and returns the refreshed states to the caller.
|
||||||
|
*/
|
||||||
|
public List<StoreRemoteState> refreshStoreReviewStatus(String versionId) {
|
||||||
|
AppVersionEntity v = versionRepo.findById(versionId).orElse(null);
|
||||||
|
if (v == null) {
|
||||||
|
throw new IllegalArgumentException("Version not found: " + versionId);
|
||||||
|
}
|
||||||
|
Map<String, Object> reviewMap;
|
||||||
|
try {
|
||||||
|
reviewMap = v.getStoreReviewStatus() != null
|
||||||
|
? mapper.readValue(v.getStoreReviewStatus(), new com.fasterxml.jackson.core.type.TypeReference<>() {})
|
||||||
|
: new LinkedHashMap<>();
|
||||||
|
} catch (Exception e) {
|
||||||
|
reviewMap = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
List<String> targets = parseTargets(v.getStoreSubmitTargets());
|
||||||
|
List<StoreRemoteState> results = new ArrayList<>();
|
||||||
|
for (String storeType : targets) {
|
||||||
|
AppStoreConfigEntity cfg;
|
||||||
|
try {
|
||||||
|
cfg = configRepo.findByAppKeyAndStoreType(v.getAppKey(),
|
||||||
|
AppStoreConfigEntity.StoreType.valueOf(storeType)).orElse(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
results.add(StoreRemoteState.failed(
|
||||||
|
AppStoreConfigEntity.StoreType.valueOf(storeType),
|
||||||
|
"Store config invalid: " + e.getMessage(),
|
||||||
|
"厂商配置无效"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cfg == null || !cfg.isEnabled()) {
|
||||||
|
results.add(StoreRemoteState.failed(
|
||||||
|
AppStoreConfigEntity.StoreType.valueOf(storeType),
|
||||||
|
"Store config not found or disabled",
|
||||||
|
"厂商未配置或已禁用"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Map<String, String> creds = parseConfig(cfg.getConfigJson());
|
||||||
|
StoreRemoteState polled = queryRemoteState(storeType, v, creds);
|
||||||
|
if (polled == null) {
|
||||||
|
results.add(StoreRemoteState.failed(
|
||||||
|
AppStoreConfigEntity.StoreType.valueOf(storeType),
|
||||||
|
"No poll API for this store",
|
||||||
|
"该厂商不支持状态查询"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String existingState = "";
|
||||||
|
Object entry = reviewMap.get(storeType);
|
||||||
|
if (entry instanceof Map<?, ?> m) {
|
||||||
|
Object s = m.get("state");
|
||||||
|
existingState = s instanceof String str ? str : "";
|
||||||
|
} else if (entry instanceof String s) {
|
||||||
|
existingState = s;
|
||||||
|
}
|
||||||
|
boolean isUnderReview = "UNDER_REVIEW".equals(existingState);
|
||||||
|
boolean isRejected = "REJECTED".equals(existingState);
|
||||||
|
AppVersionEntity.StoreReviewState mappedState = mapToStoreReviewState(polled.getReviewState());
|
||||||
|
if (isUnderReview) {
|
||||||
|
if (mappedState != AppVersionEntity.StoreReviewState.UNDER_REVIEW) {
|
||||||
|
log.info("Manual refresh: {}/{} status changed from UNDER_REVIEW to {}", v.getId(), storeType, mappedState);
|
||||||
|
if (mappedState == AppVersionEntity.StoreReviewState.APPROVED) {
|
||||||
|
storeService.updateStoreReviewLive(v.getId(), storeType, !polled.isCurrentSubmissionLive(),
|
||||||
|
buildLiveReason(polled), buildExtra(polled));
|
||||||
|
} else {
|
||||||
|
storeService.updateStoreReview(v.getId(), storeType, mappedState, "手动刷新厂商审核状态");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isRejected) {
|
||||||
|
if (polled.getReviewState() == StoreRemoteState.ReviewState.ONLINE && polled.isCurrentSubmissionLive()) {
|
||||||
|
log.info("Manual refresh: {}/{} was REJECTED but current submission is now live", v.getId(), storeType);
|
||||||
|
storeService.updateStoreReviewLive(v.getId(), storeType, false,
|
||||||
|
buildLiveReason(polled), buildExtra(polled));
|
||||||
|
} else if (polled.getReviewState() == StoreRemoteState.ReviewState.ONLINE && polled.isNonCurrentRelease()) {
|
||||||
|
log.info("Manual refresh: {}/{} was REJECTED and a different version is online — leaving REJECTED", v.getId(), storeType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For other states (PENDING, SUBMITTING, APPROVED, WITHDRAWN),
|
||||||
|
// just record the polled state without modifying the database.
|
||||||
|
log.debug("Manual refresh: {}/{} existing={} polled={} — no state transition applied",
|
||||||
|
v.getId(), storeType, existingState, polled.getReviewState());
|
||||||
|
}
|
||||||
|
results.add(polled);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Manual refresh query failed for {}/{}: {}", v.getId(), storeType, e.getMessage());
|
||||||
|
results.add(StoreRemoteState.failed(
|
||||||
|
AppStoreConfigEntity.StoreType.valueOf(storeType),
|
||||||
|
describeException(e),
|
||||||
|
"查询失败,不能确认状态"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
private AppVersionEntity.StoreReviewState mapToStoreReviewState(StoreRemoteState.ReviewState reviewState) {
|
private AppVersionEntity.StoreReviewState mapToStoreReviewState(StoreRemoteState.ReviewState reviewState) {
|
||||||
return switch (reviewState) {
|
return switch (reviewState) {
|
||||||
case ONLINE -> AppVersionEntity.StoreReviewState.APPROVED;
|
case ONLINE -> AppVersionEntity.StoreReviewState.APPROVED;
|
||||||
|
|||||||
@ -0,0 +1,169 @@
|
|||||||
|
package com.xuqm.update.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.xuqm.update.entity.AppStoreConfigEntity;
|
||||||
|
import com.xuqm.update.entity.AppVersionEntity;
|
||||||
|
import com.xuqm.update.model.StoreRemoteState;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for vendor API response parsing logic.
|
||||||
|
* Verifies Xiaomi and Vivo edge cases with non-current releases.
|
||||||
|
*/
|
||||||
|
class StoreRemoteStateParsingTest {
|
||||||
|
|
||||||
|
private static final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void xiaomiUpdateVersionTrueButDifferentOnlineVersion() throws Exception {
|
||||||
|
// Xiaomi returns updateVersion=true but onlineVersionCode is for an older release
|
||||||
|
String json = "{\"packageInfo\":{\"versionCode\":\"260513531\",\"versionName\":\"7.2.10\",\"appStatus\":1},\"updateVersion\":true}";
|
||||||
|
JsonNode root = mapper.readTree(json);
|
||||||
|
JsonNode pkg = root.path("packageInfo");
|
||||||
|
|
||||||
|
String onlineVersionCode = pkg.path("versionCode").asText("");
|
||||||
|
String onlineVersionName = pkg.path("versionName").asText("");
|
||||||
|
int appStatus = pkg.path("appStatus").asInt(-1);
|
||||||
|
boolean updateVersion = root.path("updateVersion").asBoolean(false);
|
||||||
|
String submittedCode = "260518336";
|
||||||
|
|
||||||
|
boolean currentSubmissionLive = submittedCode.equals(onlineVersionCode) && !submittedCode.isBlank();
|
||||||
|
boolean hasOnlineVersion = updateVersion || !onlineVersionCode.isBlank();
|
||||||
|
boolean nonCurrentRelease = hasOnlineVersion && !currentSubmissionLive;
|
||||||
|
|
||||||
|
StoreRemoteState.ReviewState reviewState;
|
||||||
|
if (appStatus == 5) {
|
||||||
|
reviewState = StoreRemoteState.ReviewState.REJECTED;
|
||||||
|
} else if (currentSubmissionLive) {
|
||||||
|
reviewState = StoreRemoteState.ReviewState.ONLINE;
|
||||||
|
} else {
|
||||||
|
reviewState = StoreRemoteState.ReviewState.UNDER_REVIEW_XIAOMI;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(currentSubmissionLive, "Current submission should NOT be live when version codes differ");
|
||||||
|
assertTrue(nonCurrentRelease, "Should detect non-current release when online version differs");
|
||||||
|
assertEquals(StoreRemoteState.ReviewState.UNDER_REVIEW_XIAOMI, reviewState,
|
||||||
|
"Should remain UNDER_REVIEW when updateVersion=true but online version is different");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void xiaomiCurrentSubmissionLive() throws Exception {
|
||||||
|
String json = "{\"packageInfo\":{\"versionCode\":\"260518336\",\"versionName\":\"7.2.10\",\"appStatus\":2},\"updateVersion\":true}";
|
||||||
|
JsonNode root = mapper.readTree(json);
|
||||||
|
JsonNode pkg = root.path("packageInfo");
|
||||||
|
|
||||||
|
String onlineVersionCode = pkg.path("versionCode").asText("");
|
||||||
|
String submittedCode = "260518336";
|
||||||
|
int appStatus = pkg.path("appStatus").asInt(-1);
|
||||||
|
boolean updateVersion = root.path("updateVersion").asBoolean(false);
|
||||||
|
|
||||||
|
boolean currentSubmissionLive = submittedCode.equals(onlineVersionCode) && !submittedCode.isBlank();
|
||||||
|
StoreRemoteState.ReviewState reviewState;
|
||||||
|
if (appStatus == 5) {
|
||||||
|
reviewState = StoreRemoteState.ReviewState.REJECTED;
|
||||||
|
} else if (currentSubmissionLive) {
|
||||||
|
reviewState = StoreRemoteState.ReviewState.ONLINE;
|
||||||
|
} else {
|
||||||
|
reviewState = StoreRemoteState.ReviewState.UNDER_REVIEW_XIAOMI;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(currentSubmissionLive, "Current submission should be live when version codes match");
|
||||||
|
assertEquals(StoreRemoteState.ReviewState.ONLINE, reviewState,
|
||||||
|
"Should be ONLINE when current submission matches online version");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void xiaomiExplicitRejection() throws Exception {
|
||||||
|
String json = "{\"packageInfo\":{\"versionCode\":\"260518336\",\"versionName\":\"7.2.10\",\"appStatus\":5},\"updateVersion\":false}";
|
||||||
|
JsonNode root = mapper.readTree(json);
|
||||||
|
JsonNode pkg = root.path("packageInfo");
|
||||||
|
|
||||||
|
int appStatus = pkg.path("appStatus").asInt(-1);
|
||||||
|
StoreRemoteState.ReviewState reviewState;
|
||||||
|
if (appStatus == 5) {
|
||||||
|
reviewState = StoreRemoteState.ReviewState.REJECTED;
|
||||||
|
} else {
|
||||||
|
reviewState = StoreRemoteState.ReviewState.UNDER_REVIEW_XIAOMI;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(StoreRemoteState.ReviewState.REJECTED, reviewState,
|
||||||
|
"appStatus=5 should always be REJECTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void vivoNonCurrentRelease() throws Exception {
|
||||||
|
// Vivo returns status=3 (online) but versionCode is for an older release
|
||||||
|
String json = "{\"data\":{\"status\":3,\"versionCode\":\"260513531\",\"versionName\":\"7.2.10\"}}";
|
||||||
|
JsonNode root = mapper.readTree(json);
|
||||||
|
JsonNode data = root.path("data");
|
||||||
|
|
||||||
|
int status = data.path("status").asInt(-1);
|
||||||
|
String onlineVersionCode = data.path("versionCode").asText("");
|
||||||
|
String submittedCode = "260518336";
|
||||||
|
boolean isLive = status == 3;
|
||||||
|
|
||||||
|
boolean currentSubmissionLive = isLive && submittedCode.equals(onlineVersionCode);
|
||||||
|
boolean nonCurrentRelease = isLive && !submittedCode.equals(onlineVersionCode);
|
||||||
|
|
||||||
|
assertFalse(currentSubmissionLive, "Current submission should NOT be live when version codes differ");
|
||||||
|
assertTrue(nonCurrentRelease, "Should detect non-current release when online version differs");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void vivoCurrentSubmissionLive() throws Exception {
|
||||||
|
String json = "{\"data\":{\"status\":3,\"versionCode\":\"260518336\",\"versionName\":\"7.2.10\"}}";
|
||||||
|
JsonNode root = mapper.readTree(json);
|
||||||
|
JsonNode data = root.path("data");
|
||||||
|
|
||||||
|
int status = data.path("status").asInt(-1);
|
||||||
|
String onlineVersionCode = data.path("versionCode").asText("");
|
||||||
|
String submittedCode = "260518336";
|
||||||
|
boolean isLive = status == 3;
|
||||||
|
|
||||||
|
boolean currentSubmissionLive = isLive && submittedCode.equals(onlineVersionCode);
|
||||||
|
boolean nonCurrentRelease = isLive && !submittedCode.equals(onlineVersionCode);
|
||||||
|
|
||||||
|
assertTrue(currentSubmissionLive, "Current submission should be live when version codes match");
|
||||||
|
assertFalse(nonCurrentRelease, "Should NOT be non-current release when version codes match");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void vivoMissingVersionCodeDefaultsToNonCurrentRelease() throws Exception {
|
||||||
|
// Vivo returns status=3 but no versionCode - safest assumption is non-current release
|
||||||
|
String json = "{\"data\":{\"status\":3,\"versionName\":\"7.2.10\"}}";
|
||||||
|
JsonNode root = mapper.readTree(json);
|
||||||
|
JsonNode data = root.path("data");
|
||||||
|
|
||||||
|
int status = data.path("status").asInt(-1);
|
||||||
|
String onlineVersionCode = data.path("versionCode").asText("");
|
||||||
|
String submittedCode = "260518336";
|
||||||
|
boolean isLive = status == 3;
|
||||||
|
|
||||||
|
boolean currentSubmissionLive = isLive && submittedCode.equals(onlineVersionCode);
|
||||||
|
boolean nonCurrentRelease = isLive && !submittedCode.equals(onlineVersionCode);
|
||||||
|
|
||||||
|
assertFalse(currentSubmissionLive, "Should not be current submission live when versionCode is missing");
|
||||||
|
assertTrue(nonCurrentRelease, "Should default to non-current release when versionCode is missing but status is online");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void allApprovedExcludesNonCurrentRelease() throws Exception {
|
||||||
|
// Simulate reviewMap with a store that is APPROVED but nonCurrentRelease=true
|
||||||
|
String reviewJson = "{\"VIVO\":{\"state\":\"APPROVED\",\"nonCurrentRelease\":true,\"currentSubmissionLive\":false}}";
|
||||||
|
java.util.Map<String, Object> reviewMap = mapper.readValue(reviewJson, java.util.Map.class);
|
||||||
|
|
||||||
|
// The allApproved logic should reject this because nonCurrentRelease=true
|
||||||
|
Object entry = reviewMap.get("VIVO");
|
||||||
|
assertTrue(entry instanceof java.util.Map<?, ?>);
|
||||||
|
java.util.Map<?, ?> map = (java.util.Map<?, ?>) entry;
|
||||||
|
Object nonCurrentRelease = map.get("nonCurrentRelease");
|
||||||
|
assertTrue(nonCurrentRelease instanceof Boolean && (Boolean) nonCurrentRelease);
|
||||||
|
|
||||||
|
Object currentSubmissionLive = map.get("currentSubmissionLive");
|
||||||
|
assertTrue(currentSubmissionLive instanceof Boolean && !(Boolean) currentSubmissionLive);
|
||||||
|
}
|
||||||
|
}
|
||||||
正在加载...
在新工单中引用
屏蔽一个用户