fix(goauto): preserve empty missing arrays (#170)
This commit is contained in:
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 无效")
|
||||
|
||||
Reference in New Issue
Block a user