fix(goauto): preserve empty missing arrays (#170)

This commit is contained in:
QiuSW
2026-08-31 15:17:44 +08:00
parent 78f5895a09
commit 60f01f7447
8 changed files with 90 additions and 15 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "cn.ilapage.goauto.agent"
minSdk = 23
targetSdk = 34
versionCode = 47
versionName = "0.9.34"
versionCode = 48
versionName = "0.9.35"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -456,7 +456,7 @@ class AgentApiClient(private val serverUrl: String) {
val specs = specsJson.keys().asSequence().associateWith(specsJson::getString)
HistorySku(specs, item.getLong("priceCent"), item.getBoolean("available"), item.getBoolean("complete"))
},
missing = data.getJSONArray("missing").strings(),
missing = data.stringArrayOrEmpty("missing"),
replacementEligible = data.optBoolean("replacementEligible"),
replacementDisabledReason = data.nullableString("replacementDisabledReason"),
replacementMappingStatus = data.nullableString("replacementMappingStatus"),
@@ -624,3 +624,5 @@ private fun <T> JSONArray.objects(mapper: (JSONObject) -> T): List<T> = (0 until
private fun JSONArray.strings(): List<String> = (0 until length()).map(::getString)
private fun JSONObject.nullableString(key: String): String? = if (!has(key) || isNull(key)) null else getString(key)
private fun JSONObject.nullableLong(key: String): Long? = if (!has(key) || isNull(key)) null else getLong(key)
internal fun JSONObject.stringArrayOrEmpty(key: String): List<String> = optJSONArray(key)?.strings().orEmpty()
@@ -0,0 +1,22 @@
package cn.ilapage.goauto.agent
import cn.ilapage.goauto.agent.network.stringArrayOrEmpty
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Test
class AgentApiClientJsonTest {
@Test
fun `missing arrays preserve values`() {
val data = JSONObject().put("missing", JSONArray().put("size").put("price:red"))
assertEquals(listOf("size", "price:red"), data.stringArrayOrEmpty("missing"))
}
@Test
fun `missing null or absent values become an empty list`() {
assertEquals(emptyList<String>(), JSONObject().put("missing", JSONObject.NULL).stringArrayOrEmpty("missing"))
assertEquals(emptyList<String>(), JSONObject().stringArrayOrEmpty("missing"))
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ func TestParseBuiltAgentAPKWhenAvailable(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if metadata.VersionCode != 41 || metadata.VersionName != "0.9.28" {
if metadata.VersionCode != 48 || metadata.VersionName != "0.9.35" {
t.Fatalf("metadata=%+v", metadata)
}
}
@@ -262,6 +262,31 @@ func TestNormalizeResultRejectsCurrencyColorWithPricesAndSKUs(t *testing.T) {
}
}
func TestNormalizeResultPreservesEmptyMissingArray(t *testing.T) {
normalized := normalizeResultRequest(ResultRequest{Status: models.TaskStatusCompleted, Missing: []string{}})
if normalized.Request.Missing == nil || len(normalized.Request.Missing) != 0 {
t.Fatalf("empty missing array degraded to nil: %#v", normalized.Request.Missing)
}
raw, err := json.Marshal(normalized.Request.Missing)
if err != nil || string(raw) != "[]" {
t.Fatalf("empty missing array serialized unexpectedly: %s %v", raw, err)
}
}
func TestDecodeMissingValuesNormalizesLegacyNullAndInvalidJSON(t *testing.T) {
empty, jsonNull, invalid, populated := "[]", "null", "{", `["size"]`
for name, value := range map[string]*string{
"sql-null": nil, "empty": &empty, "json-null": &jsonNull, "invalid": &invalid,
} {
if decoded := decodeMissingValues(value); decoded == nil || len(decoded) != 0 {
t.Fatalf("%s did not normalize to a non-nil empty array: %#v", name, decoded)
}
}
if decoded := decodeMissingValues(&populated); len(decoded) != 1 || decoded[0] != "size" {
t.Fatalf("populated missing values changed: %#v", decoded)
}
}
func TestResetRejectsDeletedRuleWithoutClearingResult(t *testing.T) {
db := openTaskDatabase(t)
deviceRecord, _ := registerTaskDevice(t, db, "device-one")
+1 -7
View File
@@ -2,7 +2,6 @@ package task
import (
"context"
"encoding/json"
"errors"
"strconv"
"strings"
@@ -187,12 +186,7 @@ func (service *Service) AgentHistoryDetail(ctx context.Context, taskID uint64, t
}
func agentCollectionItem(record models.CollectionTask) AgentCollectionItem {
missing := 0
if record.MissingJSON != nil {
var values []string
_ = json.Unmarshal([]byte(*record.MissingJSON), &values)
missing = len(values)
}
missing := len(decodeMissingValues(record.MissingJSON))
return AgentCollectionItem{
TaskID: record.ID, AttemptNumber: record.AttemptNumber, Status: record.Status, Source: record.Source, GoodsID: record.GoodsIDSnapshot, Title: record.Title,
MissingCount: missing, ErrorCode: record.ErrorCode, ErrorMessage: record.ErrorMessage,
@@ -73,3 +73,25 @@ func TestAgentHistoryRespectsRequestedDays(t *testing.T) {
t.Fatalf("invalid days was accepted: %v", err)
}
}
func TestAgentHistoryDetailReturnsArrayForLegacyNullMissing(t *testing.T) {
db := openTaskDatabase(t)
deviceRecord, token := registerTaskDevice(t, db, "history-null-missing")
record := createTask(t, db, &deviceRecord.ID)
legacyNull := "null"
if err := db.Model(&record).Update("missing_json", legacyNull).Error; err != nil {
t.Fatal(err)
}
detail, err := newTaskService(db).AgentHistoryDetail(context.Background(), record.ID, token)
if err != nil {
t.Fatal(err)
}
if detail.Missing == nil || len(detail.Missing) != 0 {
t.Fatalf("legacy null missing was not normalized: %#v", detail.Missing)
}
raw, err := json.Marshal(detail)
if err != nil || !strings.Contains(string(raw), `"missing":[]`) {
t.Fatalf("history detail did not preserve array contract: %s %v", raw, err)
}
}
+14 -4
View File
@@ -208,7 +208,7 @@ func normalizeResultRequest(request ResultRequest) normalizedCollectionResult {
result.Dimensions = make([]ResultDimension, 0, len(request.Dimensions))
result.ColorPrices = append([]ResultColorPrice(nil), request.ColorPrices...)
result.SKUs = make([]ResultSKU, 0, len(request.SKUs))
result.Missing = append([]string(nil), request.Missing...)
result.Missing = append(make([]string, 0, len(request.Missing)), request.Missing...)
keyCounts := map[string]int{}
for _, dimension := range request.Dimensions {
@@ -543,12 +543,22 @@ func (service *Service) Detail(ctx context.Context, taskID uint64) (DetailRespon
}
result.SKUs = append(result.SKUs, DetailSKU{ID: sku.ID, Specs: specs, PriceCent: sku.PriceCent, Available: sku.Available, Complete: sku.Complete})
}
if item.MissingJSON != nil {
_ = json.Unmarshal([]byte(*item.MissingJSON), &result.Missing)
}
result.Missing = decodeMissingValues(item.MissingJSON)
return result, nil
}
func decodeMissingValues(raw *string) []string {
values := make([]string, 0)
if raw == nil {
return values
}
var decoded []string
if json.Unmarshal([]byte(*raw), &decoded) != nil || decoded == nil {
return values
}
return decoded
}
func validateResultRequest(taskID uint64, request ResultRequest) error {
if taskID == 0 || uuid.Validate(request.RequestID) != nil || request.Product.PDDGoodsID == "" {
return serviceError("INVALID_REQUEST", "结果 requestId、taskId 或 goods_id 无效")