geopro/tests/data/test_nav_dto.cpp

402 lines
17 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include <QByteArray>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <vector>
#include "dto/NavDto.hpp"
using namespace geopro::data;
namespace {
QJsonArray arrOf(const char* json) {
return QJsonDocument::fromJson(QByteArray(json)).array();
}
} // namespace
TEST(NavDto, ParseWorkspacesMapsFieldsAndCurrentFlag) {
const auto arr = arrOf(R"([
{"id":"t1","name":"个人空间","ownerType":1,"isCurTenant":1},
{"id":"t2","name":"企业A","ownerType":2,"isCurTenant":0}
])");
const auto ws = dto::parseWorkspaces(arr);
ASSERT_EQ(ws.size(), 2u);
EXPECT_EQ(ws[0].id, "t1");
EXPECT_EQ(ws[0].ownerType, 1);
EXPECT_TRUE(ws[0].isCurrent);
EXPECT_FALSE(ws[1].isCurrent);
}
namespace {
QJsonObject objOf(const char* json) {
return QJsonDocument::fromJson(QByteArray(json)).object();
}
} // namespace
TEST(NavDto, ParseProjectsMapsCrsAndPaging) {
const auto data = objOf(R"({
"hasNextPage": true,
"projectList": [
{"id":"p1","projectName":"青海湖北岸","projectTypeName":"ERT",
"referenceCRSCode":"EPSG:4547","referenceCRSName":"CGCS2000","status":1}
]
})");
const auto page = dto::parseProjects(data);
EXPECT_TRUE(page.hasNextPage);
ASSERT_EQ(page.projects.size(), 1u);
EXPECT_EQ(page.projects[0].id, "p1");
EXPECT_EQ(page.projects[0].name, "青海湖北岸");
EXPECT_EQ(page.projects[0].typeName, "ERT");
EXPECT_EQ(page.projects[0].crsCode, "EPSG:4547");
EXPECT_EQ(page.projects[0].status, 1);
}
TEST(NavDto, ParseStructNodesMapsParentAndType) {
const auto arr = arrOf(R"([
{"id":"gs1","name":"工区1","parentId":"","type":1,"typeName":"GS","confCode":""},
{"id":"tm1","name":"测线1","parentId":"gs1","type":2,"typeName":"TM","confCode":"ERT"}
])");
const auto ns = dto::parseStructNodes(arr);
ASSERT_EQ(ns.size(), 2u);
EXPECT_EQ(ns[0].id, "gs1");
EXPECT_EQ(ns[1].parentId, "gs1");
EXPECT_EQ(ns[1].confCode, "ERT");
EXPECT_EQ(ns[1].type, 2);
}
TEST(NavDto, BuildStructTreeNestsGsTmAndDirectTm) {
const std::vector<StructNode> flat = {
{"gs1", "工区1", "", "GS", "", "", 1},
{"tm1", "测线1", "gs1", "TM", "", "", 2},
{"tm2", "测线2", "gs1", "TM", "", "", 2},
{"tmD", "直挂测线", "", "TM", "", "", 2}, // TM 直挂项目(无 GS
};
const auto roots = dto::buildStructTree(flat);
ASSERT_EQ(roots.size(), 2u); // gs1 + tmD
EXPECT_EQ(roots[0].node.id, "gs1");
EXPECT_FALSE(roots[0].isTm); // 非叶 = GS
ASSERT_EQ(roots[0].children.size(), 2u);
EXPECT_EQ(roots[0].children[0].node.id, "tm1");
EXPECT_TRUE(roots[0].children[0].isTm); // 叶 = TM
EXPECT_EQ(roots[1].node.id, "tmD");
EXPECT_TRUE(roots[1].isTm); // 直挂项目的叶子 = TM
}
TEST(NavDto, BuildStructTreeOrphanParentBecomesRoot) {
const std::vector<StructNode> flat = {
{"tmX", "孤儿测线", "ghost", "TM", "", "", 2}, // parentId 不在集合内
};
const auto roots = dto::buildStructTree(flat);
ASSERT_EQ(roots.size(), 1u);
EXPECT_EQ(roots[0].node.id, "tmX");
EXPECT_TRUE(roots[0].isTm);
}
TEST(NavDto, BuildStructTreeEmpty) {
EXPECT_TRUE(dto::buildStructTree({}).empty());
}
TEST(NavDto, BuildStructTreeHandlesCycleWithoutInfiniteRecursion) {
// 不可信数据:重复 id 形成可达环R→X→Y→重复X…。必须终止、不崩。
const std::vector<StructNode> flat = {
{"R", "", "", "GS", "", "", 1},
{"X", "x", "R", "GS", "", "", 1},
{"Y", "y", "X", "GS", "", "", 1},
{"X", "x2", "Y", "TM", "", "", 2}, // 重复 id X父=Y → 若不防环将无限递归
};
const auto roots = dto::buildStructTree(flat); // 不挂起即通过
ASSERT_EQ(roots.size(), 1u);
EXPECT_EQ(roots[0].node.id, "R");
}
TEST(NavDto, ParseProjectsEmptyAndMissingListGraceful) {
EXPECT_TRUE(dto::parseProjects(objOf(R"({})")).projects.empty());
EXPECT_FALSE(dto::parseProjects(objOf(R"({"hasNextPage":false})")).hasNextPage);
const auto p = dto::parseProjects(objOf(R"({"projectList":[]})"));
EXPECT_TRUE(p.projects.empty());
}
TEST(NavDto, ParseProjectListArrayMapsItem) {
const auto arr = arrOf(R"([
{"id":"p1","projectName":"演示","projectTypeName":"ERT","referenceCRSCode":"EPSG:4547","status":1}
])");
const auto v = dto::parseProjectList(arr);
ASSERT_EQ(v.size(), 1u);
EXPECT_EQ(v[0].id, "p1");
EXPECT_EQ(v[0].name, "演示");
EXPECT_EQ(v[0].crsCode, "EPSG:4547");
}
TEST(NavDto, BuildStructTreeDropsDsAndTmStaysLeaf) {
// 真实形态:项目(1) → TM(2) → DS(3)。DS 不进树;带 DS 子节点的 TM 仍是 TM 叶子。
// 字段序id,name,parentId,typeName,confCode,typeId,typetypeId 为新增,测试填空串)。
const std::vector<StructNode> flat = {
{"P", "项目", "0", "PRJ", "", "", 1},
{"T1", "ERT1", "P", "ERT", "ERT", "", 2},
{"D1", "批次1","T1", "", "", "", 3}, // DS应被过滤
{"T2", "ERT2", "P", "ERT", "ERT", "", 2},
};
const auto roots = dto::buildStructTree(flat);
ASSERT_EQ(roots.size(), 1u); // 仅项目根parentId "0"
EXPECT_EQ(roots[0].node.id, "P");
EXPECT_FALSE(roots[0].isTm); // 项目根 type1
ASSERT_EQ(roots[0].children.size(), 2u); // T1、T2D1 被过滤)
EXPECT_EQ(roots[0].children[0].node.id, "T1");
EXPECT_TRUE(roots[0].children[0].isTm); // TM type2
EXPECT_TRUE(roots[0].children[0].children.empty()); // DS 不进树
}
TEST(NavDto, ParseDsRowsDataAndFile) {
const auto d = dto::parseDsRows(arrOf(R"([
{"id":"d1","dsName":"ERT1-WS","name":"电阻率数据","ddCode":"dd_inversion_data","createTime":"2026-03-25 16:48:57"}
])"));
ASSERT_EQ(d.size(), 1u);
EXPECT_EQ(d[0].id, "d1");
EXPECT_EQ(d[0].dsName, "ERT1-WS");
EXPECT_EQ(d[0].typeName, "电阻率数据");
EXPECT_EQ(d[0].createTime, "2026-03-25 16:48:57");
EXPECT_TRUE(d[0].fileName.empty());
const auto f = dto::parseDsRows(arrOf(R"([
{"id":"f1","dsName":"ERT1-WS.xlsx","name":"","ddCode":"dd_file",
"file":{"name":"ERT1-WS.xlsx","size":62760,"url":"/common/file/x.xlsx"}}
])"));
ASSERT_EQ(f.size(), 1u);
EXPECT_EQ(f[0].fileName, "ERT1-WS.xlsx");
EXPECT_EQ(f[0].fileSize, 62760);
EXPECT_EQ(f[0].fileUrl, "/common/file/x.xlsx");
const auto page = dto::parseDsPage(objOf(R"({
"total": 18,
"list": [{"id":"x","dsName":"a","name":"t","ddCode":"dd","createTime":"2026-01-01 00:00:00"}]
})"));
EXPECT_EQ(page.total, 18);
ASSERT_EQ(page.rows.size(), 1u);
EXPECT_EQ(page.rows[0].dsName, "a");
}
// 数据集树父节点sourceShowParentId 优先(=显示树父),缺失回退 parentId皆无则空=树根)。
TEST(NavDto, ParseDsRowsParentIdForTree) {
const auto d = dto::parseDsRows(arrOf(R"([
{"id":"raw","dsName":"E3原始","name":"ERT原始数据","ddCode":"dd_ert_measurement_data",
"parentId":"srcFile","sourceShowParentId":"srcFile"},
{"id":"inv","dsName":"E3反演","name":"电阻率数据","ddCode":"dd_inversion_data",
"parentId":"raw","sourceShowParentId":"raw"},
{"id":"fallback","dsName":"仅parentId","name":"t","ddCode":"dd",
"parentId":"raw"},
{"id":"root","dsName":"无父","name":"t","ddCode":"dd"}
])"));
ASSERT_EQ(d.size(), 4u);
EXPECT_EQ(d[0].parentId, "srcFile"); // 原始数据→源文件节点(不在本批→树根)
EXPECT_EQ(d[1].parentId, "raw"); // 派生反演→挂原始数据下
EXPECT_EQ(d[2].parentId, "raw"); // 无 sourceShowParentId → 回退 parentId
EXPECT_TRUE(d[3].parentId.empty()); // 二者皆无 → 空(树根)
}
// 大类分类主键 dsTypeCode、层级 structParent*、原始属性 properties[] 的解析。
TEST(NavDto, ParseDsRowsExtractsTypeCodeAndProperties) {
const auto d = dto::parseDsRows(arrOf(R"([
{"id":"d1","dsName":"ERT1-WS","name":"电阻率数据",
"ddCode":"dd_inversion_data","dsTypeCode":"ERT platform inversion data",
"createTime":"2026-03-25 16:48:57","structParentId":"tm1","structParentConfType":2,
"properties":[
{"confFieldId":"1450495001706500","value":"1429468249448449"},
{"confFieldId":"1455083478786048","value":"2026-03-25 16:48:57"}
]}
])"));
ASSERT_EQ(d.size(), 1u);
EXPECT_EQ(d[0].dsTypeCode, "ERT platform inversion data");
EXPECT_EQ(d[0].structParentId, "tm1");
EXPECT_EQ(d[0].structParentConfType, 2);
EXPECT_EQ(d[0].ddCode, "dd_inversion_data");
ASSERT_EQ(d[0].properties.size(), 2u);
EXPECT_EQ(d[0].properties[0].confFieldId, "1450495001706500");
EXPECT_EQ(d[0].properties[0].value, "1429468249448449");
// 日期型 value 经 toVariant().toString() 原样保留JSON 中本就是字符串)。
EXPECT_EQ(d[0].properties[1].confFieldId, "1455083478786048");
EXPECT_EQ(d[0].properties[1].value, "2026-03-25 16:48:57");
}
TEST(NavDto, ParseProjectItemFullFields) {
const auto v = dto::parseProjectList(arrOf(R"([
{"id":"p1","projectName":"演示","projectCode":"001","status":2,
"projectTypeId":"t1","projectTypeName":"全量类型",
"ownerCompanyName":"华南所","responsiblePersonName":"张三","createTime":"2026-01-01 00:00:00"}
])"));
ASSERT_EQ(v.size(), 1u);
EXPECT_EQ(v[0].code, "001");
EXPECT_EQ(v[0].status, 2);
EXPECT_EQ(v[0].projectTypeId, "t1");
EXPECT_EQ(v[0].typeName, "全量类型");
EXPECT_EQ(v[0].ownerCompany, "华南所");
EXPECT_EQ(v[0].responsiblePerson, "张三");
EXPECT_EQ(v[0].createTime, "2026-01-01 00:00:00");
}
TEST(NavDto, ParseProjectPageAndTypes) {
const auto page =
dto::parseProjectPage(objOf(R"({"total":20,"list":[{"id":"p1","projectName":"a"}]})"));
EXPECT_EQ(page.total, 20);
ASSERT_EQ(page.rows.size(), 1u);
EXPECT_EQ(page.rows[0].name, "a");
const auto types = dto::parseProjectTypes(arrOf(R"([{"id":"t1","name":""}])"));
ASSERT_EQ(types.size(), 1u);
EXPECT_EQ(types[0].id, "t1");
EXPECT_EQ(types[0].name, "全量类型");
}
TEST(NavDto, ParseDynamicFormMergesFieldsValuesAndSorts) {
const auto data = objOf(R"({
"name": "测线1",
"properties": { "depth": "120", "len": "300", "owner": "张三" },
"formList": [
{ "groupName": "几何", "groupSort": 1, "values": [
{ "fieldName": "长度", "fieldCode": "len", "displaySort": 2 },
{ "fieldName": "深度", "fieldCode": "depth", "displaySort": 1 }
]},
{ "groupName": "归属", "groupSort": 2, "values": [
{ "fieldName": "负责人", "fieldCode": "owner", "displaySort": 1 },
{ "fieldName": "缺失项", "fieldCode": "nope", "displaySort": 2 }
]}
]
})");
const auto form = dto::parseDynamicForm(data);
EXPECT_EQ(form.name, "测线1");
ASSERT_EQ(form.groups.size(), 2u);
EXPECT_EQ(form.groups[0].name, "几何");
ASSERT_EQ(form.groups[0].fields.size(), 2u);
EXPECT_EQ(form.groups[0].fields[0].name, "深度");
EXPECT_EQ(form.groups[0].fields[0].value, "120");
EXPECT_EQ(form.groups[0].fields[1].name, "长度");
EXPECT_EQ(form.groups[0].fields[1].value, "300");
EXPECT_EQ(form.groups[1].fields[1].value, "");
}
TEST(NavDto, ParseDynamicFormEmptyFormListYieldsNoGroups) {
const auto data = objOf(R"({ "name":"", "properties":{}, "formList":[] })");
const auto form = dto::parseDynamicForm(data);
EXPECT_EQ(form.name, "");
EXPECT_TRUE(form.groups.empty());
}
TEST(NavDto, ParseExceptionsMapsFieldsAndSummary) {
const auto arr = arrOf(R"([
{ "id":"e1", "exceptionName":"空洞A", "exceptionTypeName":"空洞",
"exceptionMarkTypeName":"自动", "createTime":"2026-06-01",
"elevationList":[120.0, 80.0, 100.0], "remark":"复核中",
"consortiumId":"c1", "consortiumName":"体A", "consortiumType":"溶洞群" },
{ "id":"e2", "exceptionName":"裂隙B", "exceptionTypeName":"裂隙",
"exceptionMarkTypeName":"手动", "createTime":"2026-06-02",
"elevationList":[], "remark":"" }
])");
const auto rows = dto::parseExceptions(arr);
ASSERT_EQ(rows.size(), 2u);
EXPECT_EQ(rows[0].id, "e1");
EXPECT_EQ(rows[0].name, "空洞A");
EXPECT_EQ(rows[0].typeName, "空洞");
EXPECT_EQ(rows[0].consortiumId, "c1");
EXPECT_EQ(rows[0].consortiumName, "体A");
EXPECT_EQ(rows[0].consortiumType, "溶洞群");
EXPECT_NE(rows[0].detailSummary.find("自动"), std::string::npos);
EXPECT_NE(rows[0].detailSummary.find("2026-06-01"), std::string::npos);
EXPECT_NE(rows[0].detailSummary.find("80"), std::string::npos);
EXPECT_NE(rows[0].detailSummary.find("120"), std::string::npos);
EXPECT_NE(rows[0].detailSummary.find("复核中"), std::string::npos);
EXPECT_TRUE(rows[1].consortiumId.empty());
EXPECT_NE(rows[1].detailSummary.find("手动"), std::string::npos);
}
TEST(NavDto, GroupExceptionsByConsortiumSplitsLooseAndGroups) {
std::vector<ExceptionRow> rows = {
{ "e1","空洞A","空洞","t1","c1","体A","溶洞群","" },
{ "e2","空洞B","空洞","t1","c1","","","" },
{ "e3","裂隙X","裂隙","t1","","","","" },
{ "e4","空洞C","空洞","t1","c2","体B","溶洞群","" },
};
const auto g = dto::groupExceptionsByConsortium(rows);
ASSERT_EQ(g.consortia.size(), 2u);
EXPECT_EQ(g.consortia[0].id, "c1");
EXPECT_EQ(g.consortia[0].name, "体A");
EXPECT_EQ(g.consortia[0].typeName, "溶洞群");
ASSERT_EQ(g.consortia[0].exceptions.size(), 2u);
EXPECT_EQ(g.consortia[1].id, "c2");
ASSERT_EQ(g.loose.size(), 1u);
EXPECT_EQ(g.loose[0].id, "e3");
}
TEST(NavDto, GroupExceptionsAllLooseWhenNoConsortium) {
std::vector<ExceptionRow> rows = {
{ "e1","a","t","t1","","","","" }, { "e2","b","t","t1","","","","" } };
const auto g = dto::groupExceptionsByConsortium(rows);
EXPECT_TRUE(g.consortia.empty());
EXPECT_EQ(g.loose.size(), 2u);
}
TEST(NavDto, ParseGsTypesMapsNameAndId) {
const auto arr = arrOf(R"([
{"name":"测区","gsTypeId":"gt1"},
{"name":"测点","id":"gt2"}
])");
const auto types = dto::parseGsTypes(arr);
ASSERT_EQ(types.size(), 2u);
EXPECT_EQ(types[0].name, "测区");
EXPECT_EQ(types[0].gsTypeId, "gt1");
EXPECT_EQ(types[1].gsTypeId, "gt2"); // 回退 id
}
TEST(NavDto, ParseDsImportTypesKeepsOnlyCanImport) {
const auto arr = arrOf(R"([
{"dsTypeId":"d1","nameChn":"电阻率","canImport":true},
{"dsTypeId":"d2","nameChn":"轨迹","canImport":false}
])");
const auto t = dto::parseDsImportTypes(arr);
ASSERT_EQ(t.size(), 1u);
EXPECT_EQ(t[0].dsTypeId, "d1");
EXPECT_EQ(t[0].name, "电阻率");
EXPECT_TRUE(t[0].canImport);
}
TEST(NavDto, ParseEditableFormPreservesCompAndRequired) {
const auto data = QJsonDocument::fromJson(QByteArray(R"({
"typeId":"tt1","name":"测区",
"formList":[{"groupName":"基本","groupSort":0,"values":[
{"fieldCode":"topography","fieldName":"地形","displayComponentType":8,"requiredType":2,"displaySort":1},
{"fieldCode":"device","fieldName":"设备","displayComponentType":4,"requiredType":1,"displaySort":0,
"optionsObject":[{"label":"A","value":"a"}]}
]}],
"properties":{"topography":"山地"}
})")).object();
const auto f = dto::parseEditableForm(data, 1);
EXPECT_EQ(f.confType, 1);
ASSERT_EQ(f.groups.size(), 1u);
ASSERT_EQ(f.groups[0].fields.size(), 2u);
// 已按 displaySort 排序device(sort0) 在前。
EXPECT_EQ(f.groups[0].fields[0].code, "device");
EXPECT_EQ(f.groups[0].fields[0].comp, 4);
EXPECT_EQ(f.groups[0].fields[0].required, 1);
ASSERT_EQ(f.groups[0].fields[0].options.size(), 1u);
EXPECT_EQ(f.groups[0].fields[0].options[0].value, "a");
// topographycomp8 只读、有 properties 预填。
EXPECT_EQ(f.groups[0].fields[1].comp, 8);
EXPECT_EQ(f.groups[0].fields[1].required, 2);
EXPECT_EQ(f.groups[0].fields[1].value, "山地");
EXPECT_TRUE(f.groups[0].fields[1].fromProps);
}
TEST(NavDto, ParseEditableFormDefaultsRequiredTypeToOptional) {
// 缺省 requiredType → required==0语义可选可编辑非必填亦非只读
const auto data = QJsonDocument::fromJson(QByteArray(R"({
"typeId":"tt1","name":"测区",
"formList":[{"groupName":"基本","groupSort":0,"values":[
{"fieldCode":"note","fieldName":"备注","displayComponentType":1,"displaySort":0}
]}],
"properties":{}
})")).object();
const auto f = dto::parseEditableForm(data, 1);
ASSERT_EQ(f.groups.size(), 1u);
ASSERT_EQ(f.groups[0].fields.size(), 1u);
EXPECT_EQ(f.groups[0].fields[0].required, 0);
}