From bd1dfcb0f6b235ca67e8a00a48c813f60887a0ad Mon Sep 17 00:00:00 2001
From: "ra3battlenet-release-bot[bot]"
<5134029+ra3battlenet-release-bot[bot]@users.noreply.github.com>
Date: Mon, 9 Nov 2026 13:42:36 +0000
Subject: [PATCH 1/3] =?UTF-8?q?release:=20RA3BattleNet=20=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E6=8C=87=E9=92=88=E6=8C=87=E5=90=91=202.0.0.0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Metadata/apps/ra3battlenet/ra3battlenet.xml | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/Metadata/apps/ra3battlenet/ra3battlenet.xml b/Metadata/apps/ra3battlenet/ra3battlenet.xml
index 00dc1f4..1a319ba 100644
--- a/Metadata/apps/ra3battlenet/ra3battlenet.xml
+++ b/Metadata/apps/ra3battlenet/ra3battlenet.xml
@@ -21,7 +21,7 @@
-
+
From 4e11e6a9d6b0e8a0dff775d5f8b50096a643568f Mon Sep 17 00:00:00 2001
From: mrbbbaixue
Date: Mon, 9 Nov 2026 22:28:27 +0800
Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=93=A6=20chore(ra3battlenet):=20?=
=?UTF-8?q?=E6=A1=8C=E9=9D=A2=E7=AB=AF=E5=8F=AA=E4=BF=9D=E7=95=99=202.0.0.?=
=?UTF-8?q?0=20=E5=AE=89=E8=A3=85=E5=8C=85=E4=B8=8E=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E7=BA=BF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
清掉 2.0.0.0 之前的 Updater,安装包从 1.9.9.22 换成 2.0.0.0 的 BT 源。
---
.../manifests/{1.9.9.22.xml => 2.0.0.0.xml} | 8 ++--
.../apps/ra3battlenet/manifests/manifests.xml | 2 +-
Metadata/apps/ra3battlenet/ra3battlenet.xml | 38 ++-----------------
3 files changed, 9 insertions(+), 39 deletions(-)
rename Metadata/apps/ra3battlenet/manifests/{1.9.9.22.xml => 2.0.0.0.xml} (58%)
diff --git a/Metadata/apps/ra3battlenet/manifests/1.9.9.22.xml b/Metadata/apps/ra3battlenet/manifests/2.0.0.0.xml
similarity index 58%
rename from Metadata/apps/ra3battlenet/manifests/1.9.9.22.xml
rename to Metadata/apps/ra3battlenet/manifests/2.0.0.0.xml
index bd30e61..71a3ed4 100644
--- a/Metadata/apps/ra3battlenet/manifests/1.9.9.22.xml
+++ b/Metadata/apps/ra3battlenet/manifests/2.0.0.0.xml
@@ -3,13 +3,13 @@
${TIMESTAMP}
-
-
- Ra3BattleNet_Setup_1.9.9.22.exe
+
+
+ Ra3BattleNet_Setup_2.0.0.0.exe
/
APPLICATION
-
+
diff --git a/Metadata/apps/ra3battlenet/manifests/manifests.xml b/Metadata/apps/ra3battlenet/manifests/manifests.xml
index edabf5a..cd8ee44 100644
--- a/Metadata/apps/ra3battlenet/manifests/manifests.xml
+++ b/Metadata/apps/ra3battlenet/manifests/manifests.xml
@@ -7,6 +7,6 @@
-
+
\ No newline at end of file
diff --git a/Metadata/apps/ra3battlenet/ra3battlenet.xml b/Metadata/apps/ra3battlenet/ra3battlenet.xml
index 1a319ba..7114113 100644
--- a/Metadata/apps/ra3battlenet/ra3battlenet.xml
+++ b/Metadata/apps/ra3battlenet/ra3battlenet.xml
@@ -9,14 +9,14 @@
- 1.9.9.22
+ 2.0.0.0
红色警戒3战网服务
Ra3BattleNet
false
-
- 2026-11-03
- manifest-1.9.9.22
+
+ 2026-11-09
+ manifest-2.0.0.0
@@ -24,36 +24,6 @@
-
-
-
-
-
Date: Tue, 10 Nov 2026 00:07:22 +0800
Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A7=AA=20test(metadata):=20=E6=B8=85?=
=?UTF-8?q?=E5=8D=95=E6=96=AD=E8=A8=80=E6=94=B9=E7=94=A8=E5=81=87=E6=B8=85?=
=?UTF-8?q?=E5=8D=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
仓库样本里的版本号不再当断言,限定 ID、叶子发布和 Skudef 保序改由临时假清单验证。
---
.agents/docs/generated/notes-index.md | 3 +-
.../2026-11-09-mock-manifest-fixtures.md | 26 +++
.../ManifestParseTests.cs | 4 +-
.../PublishSurfaceTests.cs | 172 +++++++++++++-----
Ra3.BattleNet.Metadata.Tests/QueryApiTests.cs | 5 +-
Ra3.BattleNet.Metadata.Tests/StageATests.cs | 110 ++++++++---
6 files changed, 252 insertions(+), 68 deletions(-)
create mode 100644 .agents/notes/implemented/testing/2026-11-09-mock-manifest-fixtures.md
diff --git a/.agents/docs/generated/notes-index.md b/.agents/docs/generated/notes-index.md
index 5c3af13..ed33ea1 100644
--- a/.agents/docs/generated/notes-index.md
+++ b/.agents/docs/generated/notes-index.md
@@ -2,10 +2,11 @@
> 自动生成,勿手改。重新生成:`node scripts/verify-docs.mjs --write`。
-共 15 篇。按状态排序:提案 → 已实施 → 已否决。
+共 16 篇。按状态排序:提案 → 已实施 → 已否决。
| 日期 | 状态 | 分类 | 标题 | 问题 |
|---|---|---|---|---|
+| 2026-11-09 | implemented | testing | [清单行为用假清单测](../../notes/implemented/testing/2026-11-09-mock-manifest-fixtures.md) | 发布面和展平测试直接断言仓库里的真实清单路径与版本号。 |
| 2026-11-06 | implemented | feature | [WebView2 zstd 包采用双层内容指纹](../../notes/implemented/feature/2026-11-06-webview2-raw-fingerprint.md) | 旧 WebView2 清单以 Compression="zip"、HashAlgorithm="None" 描述解包目录,不能验证下载物和待安装 ZIP 的内容。 |
| 2026-11-04 | implemented | process | [Cloudflare 部署统一使用 Workers 静态资源](../../notes/implemented/process/2026-11-04-cloudflare-workers-deployment.md) | Cloudflare 后台项目是 Workers,但 npm 发布和本地服务仍调用 Pages 命令。 |
| 2026-11-04 | implemented | process | [分支预览迁移到 Worker Previews](../../notes/implemented/process/2026-11-04-worker-previews.md) | 此前部署决定使用 wrangler versions upload 创建版本预览 URL,用户现在选择 Cloudflare 新的 Worker Previews 分支预览机制。 |
diff --git a/.agents/notes/implemented/testing/2026-11-09-mock-manifest-fixtures.md b/.agents/notes/implemented/testing/2026-11-09-mock-manifest-fixtures.md
new file mode 100644
index 0000000..042f465
--- /dev/null
+++ b/.agents/notes/implemented/testing/2026-11-09-mock-manifest-fixtures.md
@@ -0,0 +1,26 @@
+# Agent Note: 清单行为用假清单测
+
+Status: implemented
+
+## Problem
+
+发布面和展平测试直接断言仓库里的真实清单路径与版本号。桌面端从 `1.9.9.22` 换成 `2.0.0.0` 之后,`PublishSurfaceTests` 和 `StageATests` 仍在找旧文件,CI 因此失败。清单版本会随发布变化,测试不该跟着改。
+
+## Decision
+
+清单的限定 ID、叶子发布、变量替换和 Skudef 保序,全部用临时目录里的假清单验证。假清单走与正式数据相同的 `Include` 链,版本号固定为测试自己的 `9.9.9` / `1.0`,不读取 `Metadata/apps` 或 `Metadata/mods` 里的现行版本。
+
+仓库样本测试只保留与具体清单无关的断言:发布物包含登记节点指向的资源,查询结果与登记 `Source` 一致。
+
+## Alternatives considered
+
+- **把写死的版本号改成 2.0.0.0**:能让这次 CI 通过,但下一次发布还是会断。
+- **从仓库样本动态读当前清单再断言**:不写死版本,却仍依赖线上数据的目录和字段,样本一改测试就漂。
+
+## Consequences
+
+发布换版本时不用再改单元测试。假清单必须自己满足 XSD 和构建校验,缺字段会在测试里失败,而不是在真实数据里被放过。
+
+## Verification
+
+`dotnet test Metadata.sln --filter "FullyQualifiedName~StageATests|FullyQualifiedName~PublishSurfaceTests|FullyQualifiedName~ManifestParseTests|FullyQualifiedName~QueryApiTests"`
diff --git a/Ra3.BattleNet.Metadata.Tests/ManifestParseTests.cs b/Ra3.BattleNet.Metadata.Tests/ManifestParseTests.cs
index 375155e..0d7eaea 100644
--- a/Ra3.BattleNet.Metadata.Tests/ManifestParseTests.cs
+++ b/Ra3.BattleNet.Metadata.Tests/ManifestParseTests.cs
@@ -254,10 +254,10 @@ public void ManifestRegistration_ResolvesRegisteredId_AndNullWhenMissing()
var root = MetadataBuilder.Load(Path.Combine(dst, "metadata.xml"));
var registered = root.GetAllElements("Manifest")
- .First(m => MetadataFlattener.LocalId(m.Get("ID")) == "manifest-3259");
+ .First(m => !string.IsNullOrWhiteSpace(m.Get("ID")) && !string.IsNullOrWhiteSpace(m.Get("Source")));
var found = root.ManifestRegistration(registered.Get("ID")!);
found.Should().BeSameAs(registered);
- found!.Get("Source").Should().EndWith("manifests/3.259.xml");
+ found!.Get("Source")!.Replace('\\', '/').Should().Contain("manifests/");
root.ManifestRegistration("no-such-manifest").Should().BeNull();
}
diff --git a/Ra3.BattleNet.Metadata.Tests/PublishSurfaceTests.cs b/Ra3.BattleNet.Metadata.Tests/PublishSurfaceTests.cs
index eb62a88..e26b8cf 100644
--- a/Ra3.BattleNet.Metadata.Tests/PublishSurfaceTests.cs
+++ b/Ra3.BattleNet.Metadata.Tests/PublishSurfaceTests.cs
@@ -44,8 +44,12 @@ public void Build_RepoSample_OutputHasOnlyFlattenedAndReferencedResources()
File.ReadAllBytes(Path.Combine(dst, relative))
.Should().Equal(File.ReadAllBytes(Path.Combine(src, relative)));
}
- File.Exists(Path.Combine(dst, "mods", "corona", "manifests", "3.259.xml")).Should().BeTrue();
- File.Exists(Path.Combine(dst, "apps", "ra3battlenet", "manifests", "1.9.9.22.xml")).Should().BeTrue();
+ foreach (var manifest in loaded.GetAllElements("Manifest").Where(m => m.Get("Source") is not null))
+ {
+ var relative = manifest.Get("Source")!.Replace('/', Path.DirectorySeparatorChar);
+ File.Exists(Path.Combine(dst, relative)).Should().BeTrue($"登记清单必须进发布物: {relative}");
+ File.Exists(Path.Combine(src, relative)).Should().BeTrue();
+ }
// _redirects 自动带出
File.Exists(Path.Combine(dst, "_redirects")).Should().BeTrue();
@@ -104,16 +108,123 @@ public void Build_UrlOnlyImage_CopiesNothingForIt()
}
[TestMethod]
- public void Build_RepoSample_LeafManifestIsFlattenedAndLoadable()
+ public void Build_MockLeafManifest_IsFlattenedAndLoadable()
{
- var dst = Path.Combine(Path.GetTempPath(), $"publish-leaf-{Guid.NewGuid():N}");
-
+ var temp = Path.Combine(Path.GetTempPath(), $"publish-mock-leaf-{Guid.NewGuid():N}");
+ var src = Path.Combine(temp, "src");
+ var dst = Path.Combine(temp, "out");
+ SeedSchemas(src);
try
{
- MetadataBuilder.Build(RepoMetadataDir, dst, schemaVersion: "1.0", contentRevision: "publish-test");
+ Directory.CreateDirectory(Path.Combine(src, "mods", "mockmod", "manifests"));
+ Directory.CreateDirectory(Path.Combine(src, "apps", "mockruntime", "manifests"));
+ File.WriteAllText(Path.Combine(src, "metadata.xml"), """
+
+
+
+
+
+
+
+ """);
+ File.WriteAllText(Path.Combine(src, "mods", "mockmod", "mockmod.xml"), """
+
+
+
+
+
+
+ 1.0
+ 假模组
+ MockMod
+
+
+ manifest-mock
+
+
+
+
+
+ """);
+ File.WriteAllText(Path.Combine(src, "mods", "mockmod", "manifests", "1.0.xml"), """
+
+
+
+ ${TIMESTAMP}
+
+
+
+
+
+
+
+
+
+
+ core.big
+ /
+ MISC;
+
+
+
+
+
+ text.big
+ /
+ MISC;
+
+
+
+
+
+ base.lyi
+ /
+ MOD;
+
+
+
+
+
+
+ """);
+ File.WriteAllText(Path.Combine(src, "apps", "mockruntime", "mockruntime.xml"), """
+
+
+
+
+
+
+ 1.0
+ 假运行时
+ MockRuntime
+
+
+ manifest-runtime
+
+
+
+
+
+ """);
+ File.WriteAllText(Path.Combine(src, "apps", "mockruntime", "manifests", "1.0.xml"), """
+
+
+
+
+ runtime.zip
+ /
+ RUNTIME
+
+
+
+
+
+
+ """);
+
+ MetadataBuilder.Build(src, dst, schemaVersion: "1.0", contentRevision: "publish-mock");
- // 叶子清单不能照抄源文件:客户端解析器拒绝 Includes,也不该收到未替换的变量
- var leafPath = Path.Combine(dst, "mods", "corona", "manifests", "3.259.xml");
+ var leafPath = Path.Combine(dst, "mods", "mockmod", "manifests", "1.0.xml");
var text = File.ReadAllText(leafPath);
text.Should().NotContain(" f.Sources.Count > 0);
entry.Files.Should().Contain(f => f.DownloadName != null);
- var runtime = MetadataBuilder.Load(Path.Combine(dst, "apps", "webview2", "manifests", "156.0.4314.58.xml"))
+ var runtime = MetadataBuilder.Load(Path.Combine(dst, "apps", "mockruntime", "manifests", "1.0.xml"))
.Find("Manifest")!.ToManifestEntry();
runtime.HashAlgorithm.Should().Be("SHA256");
var file = runtime.Files.Should().ContainSingle().Subject;
- file.FileName.Should().Be("WebView2Runtime.zip");
- file.DownloadName.Should().Be("WebView2Runtime.zip.zst");
+ file.FileName.Should().Be("runtime.zip");
+ file.DownloadName.Should().Be("runtime.zip.zst");
file.Compression.Should().Be("zstd");
- file.Size.Should().Be(233263294);
+ file.Size.Should().Be(64);
file.Hash.Should().Be("31de4e57a19017eee55e6f66ddb9f6c217f944716dee2cd14a37f72dfbbe831b");
file.RawHash.Should().Be("b601d79b38720d174c17b6affed3506f01c5033d20a42d1f90fe5745f1b8d021");
file.RawHashAlgorithm.Should().Be("SHA256");
file.Sources.Should().ContainSingle().Which.Url.Should()
- .Be("https://file.cor-games.com/srv/redist/WebView2Runtime.zip.zst.torrent");
+ .Be("https://example.invalid/runtime.zip.zst.torrent");
var exported = MetadataXml.Write(file.Raw);
exported.Should().Contain($"RawHash=\"{file.RawHash}\"").And.Contain("RawHashAlgorithm=\"SHA256\"");
- }
- finally
- {
- if (Directory.Exists(dst))
- Directory.Delete(dst, recursive: true);
- }
- }
- [TestMethod]
- public void Build_RepoSample_SkudefSurvivesFlattenInDeclaredOrder()
- {
- var dst = Path.Combine(Path.GetTempPath(), $"publish-skudef-{Guid.NewGuid():N}");
-
- try
- {
- MetadataBuilder.Build(RepoMetadataDir, dst, schemaVersion: "1.0", contentRevision: "publish-test");
-
- var entry = MetadataBuilder.Load(Path.Combine(dst, "mods", "corona", "manifests", "3.259.xml"))
- .Find("Manifest")!.ToManifestEntry();
var skudef = entry.Skudef;
-
skudef.Should().NotBeNull("展平后叶子清单要保住 Skudef");
skudef!.GameVersion.Should().Be("1.12");
skudef.Commands.Select(c => c.Target).Should().ContainInOrder(
- "CustomConfig.txt", "Disabler.big", "HighResShadow.big", "Cor_ENG_3.250.big",
- "coronaBGM_3.228.lyi", "corona_3.259.lyi", "StaticVersion.big");
+ "CustomConfig.txt", "core.big", "text.big", "base.lyi");
skudef.Commands.Should().ContainSingle(c => c.Target == "CustomConfig.txt" && c.Kind == SkudefCommandKind.Config && c.Optional);
- skudef.Commands.Should().Contain(c => c.Kind == SkudefCommandKind.Big && c.Target == "Disabler.big" && c.Package == "disable-sky-and-patches");
- skudef.Commands.Should().Contain(c => c.Kind == SkudefCommandKind.Big && c.Target == "HighResShadow.big" && c.Package == "hd-shadow");
- skudef.Commands.Should().Contain(c => c.Kind == SkudefCommandKind.Big && c.Target == "Cor_ENG_3.250.big" && c.Language == "en");
- skudef.Commands.Should().Contain(c => c.Kind == SkudefCommandKind.Big && c.Target == "StaticVersion.big" && c.Package == "world-builder");
-
- // 加载条件单源:File 上不能再留 Mount/Language/Package
+ skudef.Commands.Should().Contain(c => c.Kind == SkudefCommandKind.Big && c.Target == "core.big" && c.Package == "optional-pack");
+ skudef.Commands.Should().Contain(c => c.Kind == SkudefCommandKind.Big && c.Target == "text.big" && c.Language == "en");
entry.Files.Should().OnlyContain(f => f.Mount == "base" && f.Language == null && f.Package == null);
}
finally
{
- if (Directory.Exists(dst))
- Directory.Delete(dst, recursive: true);
+ if (Directory.Exists(temp))
+ Directory.Delete(temp, recursive: true);
}
}
+
}
diff --git a/Ra3.BattleNet.Metadata.Tests/QueryApiTests.cs b/Ra3.BattleNet.Metadata.Tests/QueryApiTests.cs
index 5e65f97..61095f5 100644
--- a/Ra3.BattleNet.Metadata.Tests/QueryApiTests.cs
+++ b/Ra3.BattleNet.Metadata.Tests/QueryApiTests.cs
@@ -64,7 +64,10 @@ public void Mod_ManifestSource_ResolvesRegisteredLeafManifest()
var corona = root.Catalog().Mod("Corona")!;
corona.Package().Should().NotBeNull("版本缺省取实体当前版本");
- corona.ManifestSource().Replace('\\', '/').Should().EndWith("manifests/3.259.xml");
+ var declared = corona.Package()!.ManifestId;
+ declared.Should().NotBeNullOrWhiteSpace();
+ corona.ManifestSource().Replace('\\', '/').Should().Be(
+ root.ManifestRegistration(declared!)!.Get("Source")!.Replace('\\', '/'));
var act = () => corona.ManifestSource("9.9.9");
act.Should().Throw().WithMessage("*没有版本*");
diff --git a/Ra3.BattleNet.Metadata.Tests/StageATests.cs b/Ra3.BattleNet.Metadata.Tests/StageATests.cs
index bcf4ce2..b703364 100644
--- a/Ra3.BattleNet.Metadata.Tests/StageATests.cs
+++ b/Ra3.BattleNet.Metadata.Tests/StageATests.cs
@@ -1,4 +1,4 @@
-using System.Xml.Linq;
+using System.Xml.Linq;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -45,27 +45,7 @@ public void Build_RepoSample_FlattensWithoutInclude_AndResolvesIds()
loaded.Applications().Should().Contain(a => a.Id == "RA3BattleNet");
loaded.Mods().Should().Contain(m => m.Id == "Corona");
- // 发布 ID = {路径前缀}:{localId}
- var registries = loaded.GetAllElements("Manifest")
- .Where(m => !string.IsNullOrEmpty(m.Get("ID"))).ToList();
- var manifest = registries
- .First(m => MetadataFlattener.LocalId(m.Get("ID")) == "manifest-1.9.9.22");
- manifest.Get("ID")!.Should().Contain(":");
- manifest.Get("ID")!.Should().EndWith(":manifest-1.9.9.22");
- var source = manifest.Get("Source");
- source.Should().NotBeNullOrWhiteSpace();
- source!.Replace('\\', '/').Should().EndWith("manifests/1.9.9.22.xml");
- source.Should().NotContain("apps.xml");
- var manifestPath = Path.Combine(dst, source.Replace('/', Path.DirectorySeparatorChar));
- File.Exists(manifestPath).Should().BeTrue();
- File.ReadAllText(manifestPath).Should().Contain(" MetadataFlattener.LocalId(m.Get("ID")!) == "manifest-3259");
- coronaManifest.Get("Source")!.Replace('\\', '/').Should().EndWith("manifests/3.259.xml");
-
// 引用已改写为限定 ID(示例数据里只剩 Mod 带新闻)
- var app = loaded.Applications().Single(a => a.Id == "RA3BattleNet");
var modDoc = XDocument.Load(Path.Combine(src, "mods", "corona", "corona.xml"));
var modXml = Path.Combine(src, "mods", "corona", "corona.xml");
var localContentId = modDoc.Descendants("Mod")
@@ -113,7 +93,6 @@ public void Build_RepoSample_FlattensWithoutInclude_AndResolvesIds()
File.ReadAllText(Path.Combine(dst, markdown.Source!.Replace('/', Path.DirectorySeparatorChar)))
.Should().Be(sourceBody);
- app.Packages.Single(p => p.Version == "1.9.9.22").ManifestId.Should().Be(manifest.Get("ID"));
var corona = loaded.Mods().Single(m => m.Id == "Corona");
var icon = loaded.Images().Single(image => image.Id == corona.Icon);
var sourceDoc = XDocument.Load(Path.Combine(src, "mods", "corona", "corona.xml"));
@@ -127,6 +106,93 @@ public void Build_RepoSample_FlattensWithoutInclude_AndResolvesIds()
}
}
+ [TestMethod]
+ public void Build_MockManifest_QualifiesIdAndPublishesLeaf()
+ {
+ var temp = Path.Combine(Path.GetTempPath(), $"stage-a-mock-{Guid.NewGuid():N}");
+ var src = Path.Combine(temp, "src");
+ var dst = Path.Combine(temp, "out");
+ SeedSchemas(src);
+ try
+ {
+ Directory.CreateDirectory(Path.Combine(src, "apps", "mockapp", "manifests"));
+ File.WriteAllText(Path.Combine(src, "metadata.xml"), """
+
+
+
+
+
+
+ """);
+ File.WriteAllText(Path.Combine(src, "apps", "mockapp", "mockapp.xml"), """
+
+
+
+
+
+
+ 9.9.9
+ 假应用
+ MockApp
+
+
+ manifest-mock
+
+
+
+
+
+ """);
+ File.WriteAllText(Path.Combine(src, "apps", "mockapp", "manifests", "manifests.xml"), """
+
+
+
+
+
+
+ """);
+ File.WriteAllText(Path.Combine(src, "apps", "mockapp", "manifests", "9.9.9.xml"), """
+
+
+
+ ${TIMESTAMP}
+
+
+
+ MockApp_Setup.exe
+ /
+ APPLICATION
+
+
+
+
+
+
+ """);
+
+ MetadataBuilder.Build(src, dst, schemaVersion: "1.0", contentRevision: "mock-rev");
+
+ var loaded = MetadataBuilder.Load(Path.Combine(dst, "metadata.xml"));
+ var manifest = loaded.GetAllElements("Manifest")
+ .Single(m => MetadataFlattener.LocalId(m.Get("ID")) == "manifest-mock");
+ manifest.Get("ID").Should().Be("apps/mockapp/manifests/9.9.9:manifest-mock");
+ var source = manifest.Get("Source")!.Replace('\\', '/');
+ source.Should().Be("apps/mockapp/manifests/9.9.9.xml");
+ source.Should().NotContain("apps.xml");
+
+ var manifestPath = Path.Combine(dst, source.Replace('/', Path.DirectorySeparatorChar));
+ var leaf = File.ReadAllText(manifestPath);
+ leaf.Should().Contain("