geopro/tests/data/test_nav_dto.cpp

312 lines
13 KiB
C++
Raw 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()); // 二者皆无 → 空(树根)
}
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);
}