#![cfg(all(target_os = "windows", feature = "sdk-discovery"))]
use msixbundle::*;
use std::path::Path;
use tempfile::tempdir;
fn create_minimal_appx_content(dir: &Path) {
let manifest = r#"<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities">
<Identity Name="TestCompany.TestApp" Version="1.0.0.0"
Publisher="CN=Test" ProcessorArchitecture="x64"/>
<Properties>
<DisplayName>TestApp</DisplayName>
<PublisherDisplayName>Test Company</PublisherDisplayName>
<Logo>Assets\logo.png</Logo>
</Properties>
<Resources>
<Resource Language="en-us"/>
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22621.0"/>
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust"/>
</Capabilities>
<Applications>
<Application Id="App" Executable="app.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="TestApp" Description="Test"
Square150x150Logo="Assets\logo.png" Square44x44Logo="Assets\logo.png"
BackgroundColor="transparent"/>
</Application>
</Applications>
</Package>"#;
std::fs::write(dir.join("AppxManifest.xml"), manifest).expect("write manifest");
std::fs::create_dir_all(dir.join("Assets")).expect("create Assets dir");
let png = include_bytes!("fixtures/logo.png");
std::fs::write(dir.join("Assets").join("logo.png"), png).expect("write logo");
std::fs::write(dir.join("app.exe"), b"MZ").expect("write exe");
}
#[test]
fn test_locate_sdk_tools() {
let tools = locate_sdk_tools().expect("SDK should be installed");
assert!(tools.makeappx.exists());
}
#[test]
fn test_pack_arch() {
let tools = locate_sdk_tools().expect("locate SDK");
let content_dir = tempdir().expect("create content dir");
let out_dir = tempdir().expect("create out dir");
create_minimal_appx_content(content_dir.path());
let info = read_manifest_info(content_dir.path()).expect("read manifest");
let msix = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
false,
)
.expect("pack");
assert!(msix.exists());
assert!(msix.to_string_lossy().ends_with(".msix"));
let unpack_dir = tempdir().expect("create unpack dir");
let status = std::process::Command::new(&tools.makeappx)
.args([
"unpack",
"/p",
&msix.to_string_lossy(),
"/d",
&unpack_dir.path().to_string_lossy(),
"/o", ])
.status()
.expect("run unpack");
assert!(
status.success(),
"MakeAppx unpack should succeed for valid package"
);
assert!(unpack_dir.path().join("AppxManifest.xml").exists());
assert!(unpack_dir.path().join("AppxBlockMap.xml").exists());
let manifest_content =
std::fs::read_to_string(unpack_dir.path().join("AppxManifest.xml")).expect("read manifest");
assert!(
manifest_content.contains("<Package"),
"should have Package element"
);
assert!(
manifest_content.contains("TestCompany.TestApp"),
"should have Identity Name"
);
assert!(manifest_content.contains("1.0.0.0"), "should have Version");
let blockmap_content =
std::fs::read_to_string(unpack_dir.path().join("AppxBlockMap.xml")).expect("read blockmap");
assert!(
blockmap_content.contains("<BlockMap"),
"should have BlockMap element"
);
assert!(unpack_dir.path().join("Assets").join("logo.png").exists());
assert!(unpack_dir.path().join("app.exe").exists());
}
#[test]
fn test_build_bundle() {
let tools = locate_sdk_tools().expect("locate SDK");
let content_dir = tempdir().expect("create content dir");
let out_dir = tempdir().expect("create out dir");
create_minimal_appx_content(content_dir.path());
let info = read_manifest_info(content_dir.path()).expect("read manifest");
let msix = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
false,
)
.expect("pack");
let built = vec![("x64".to_string(), msix)];
let bundle = build_bundle(&tools, out_dir.path(), &built, &info, false).expect("build bundle");
assert!(bundle.exists());
assert!(bundle.to_string_lossy().ends_with(".msixbundle"));
let unbundle_dir = tempdir().expect("create unbundle dir");
let status = std::process::Command::new(&tools.makeappx)
.args([
"unbundle",
"/p",
&bundle.to_string_lossy(),
"/d",
&unbundle_dir.path().to_string_lossy(),
"/o", ])
.status()
.expect("run unbundle");
assert!(
status.success(),
"MakeAppx unbundle should succeed for valid bundle"
);
assert!(
unbundle_dir
.path()
.join("AppxMetadata")
.join("AppxBundleManifest.xml")
.exists()
);
let bundle_manifest = std::fs::read_to_string(
unbundle_dir
.path()
.join("AppxMetadata")
.join("AppxBundleManifest.xml"),
)
.expect("read bundle manifest");
assert!(
bundle_manifest.contains("<Bundle"),
"should have Bundle element"
);
assert!(
bundle_manifest.contains("TestApp"),
"should reference the app"
);
let msix_files: Vec<_> = std::fs::read_dir(unbundle_dir.path())
.expect("read unbundle dir")
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.extension()
.map(|ext| ext == "msix")
.unwrap_or(false)
})
.collect();
assert!(
!msix_files.is_empty(),
"Bundle should contain at least one .msix"
);
let msix_name = msix_files[0].file_name();
let msix_name_str = msix_name.to_string_lossy();
assert!(
msix_name_str.contains("TestApp"),
"MSIX filename should contain app name"
);
assert!(
msix_name_str.contains("1.0.0.0"),
"MSIX filename should contain version"
);
}
fn create_appx_content_for_arch(dir: &Path, arch: &str) {
let manifest = format!(
r#"<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities">
<Identity Name="TestCompany.TestApp" Version="1.0.0.0"
Publisher="CN=Test" ProcessorArchitecture="{arch}"/>
<Properties>
<DisplayName>TestApp</DisplayName>
<PublisherDisplayName>Test Company</PublisherDisplayName>
<Logo>Assets\logo.png</Logo>
</Properties>
<Resources>
<Resource Language="en-us"/>
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22621.0"/>
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust"/>
</Capabilities>
<Applications>
<Application Id="App" Executable="app.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="TestApp" Description="Test"
Square150x150Logo="Assets\logo.png" Square44x44Logo="Assets\logo.png"
BackgroundColor="transparent"/>
</Application>
</Applications>
</Package>"#
);
std::fs::write(dir.join("AppxManifest.xml"), manifest).expect("write manifest");
std::fs::create_dir_all(dir.join("Assets")).expect("create Assets dir");
let png = include_bytes!("fixtures/logo.png");
std::fs::write(dir.join("Assets").join("logo.png"), png).expect("write logo");
std::fs::write(dir.join("app.exe"), b"MZ").expect("write exe");
}
#[test]
fn test_multi_arch_bundle() {
let tools = locate_sdk_tools().expect("locate SDK");
let x64_dir = tempdir().expect("create x64 dir");
let arm64_dir = tempdir().expect("create arm64 dir");
let out_dir = tempdir().expect("create out dir");
create_appx_content_for_arch(x64_dir.path(), "x64");
create_appx_content_for_arch(arm64_dir.path(), "arm64");
if tools.makepri.is_some() {
compile_resources_pri(
&tools,
&PriOptions {
appx_content_dir: x64_dir.path(),
default_language: "en-us",
target_os_version: "10.0.0",
keep_priconfig: false,
overwrite: true,
makepri_override: None,
},
)
.expect("compile x64 resources.pri");
compile_resources_pri(
&tools,
&PriOptions {
appx_content_dir: arm64_dir.path(),
default_language: "en-us",
target_os_version: "10.0.0",
keep_priconfig: false,
overwrite: true,
makepri_override: None,
},
)
.expect("compile arm64 resources.pri");
}
let info = read_manifest_info(x64_dir.path()).expect("read manifest");
let msix_x64 =
pack_arch(&tools, x64_dir.path(), out_dir.path(), &info, "x64", false).expect("pack x64");
let msix_arm64 = pack_arch(
&tools,
arm64_dir.path(),
out_dir.path(),
&info,
"arm64",
false,
)
.expect("pack arm64");
let built = vec![
("x64".to_string(), msix_x64),
("arm64".to_string(), msix_arm64),
];
let bundle = build_bundle(&tools, out_dir.path(), &built, &info, false).expect("build bundle");
assert!(bundle.exists());
let unbundle_dir = tempdir().expect("create unbundle dir");
let status = std::process::Command::new(&tools.makeappx)
.args([
"unbundle",
"/p",
&bundle.to_string_lossy(),
"/d",
&unbundle_dir.path().to_string_lossy(),
"/o",
])
.status()
.expect("run unbundle");
assert!(
status.success(),
"MakeAppx unbundle should succeed for multi-arch bundle"
);
let msix_files: Vec<_> = std::fs::read_dir(unbundle_dir.path())
.expect("read unbundle dir")
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.extension()
.map(|ext| ext == "msix")
.unwrap_or(false)
})
.collect();
assert_eq!(msix_files.len(), 2, "Bundle should contain two .msix files");
let bundle_manifest = std::fs::read_to_string(
unbundle_dir
.path()
.join("AppxMetadata")
.join("AppxBundleManifest.xml"),
)
.expect("read bundle manifest");
assert!(
bundle_manifest.contains("x64") || bundle_manifest.contains("X64"),
"should reference x64"
);
assert!(
bundle_manifest.contains("arm64") || bundle_manifest.contains("Arm64"),
"should reference arm64"
);
}
#[test]
fn test_bundlemap_format() {
let tools = locate_sdk_tools().expect("locate SDK");
let content_dir = tempdir().expect("create content dir");
let out_dir = tempdir().expect("create out dir");
create_minimal_appx_content(content_dir.path());
let info = read_manifest_info(content_dir.path()).expect("read manifest");
let msix = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
false,
)
.expect("pack");
let built = vec![("x64".to_string(), msix.clone())];
let _bundle = build_bundle(&tools, out_dir.path(), &built, &info, false).expect("build bundle");
let bundlemap_path = out_dir.path().join("bundlemap.txt");
assert!(bundlemap_path.exists(), "bundlemap.txt should exist");
let bundlemap_content = std::fs::read_to_string(&bundlemap_path).expect("read bundlemap");
assert!(
bundlemap_content.starts_with("[Files]"),
"bundlemap should start with [Files]"
);
let msix_filename = msix.file_name().expect("msix filename");
assert!(
bundlemap_content.contains(&format!("\"{}\"", msix_filename.to_string_lossy())),
"bundlemap should contain quoted destination filename"
);
}
#[test]
fn test_read_manifest_missing_file() {
let dir = tempdir().expect("create temp dir");
let result = read_manifest_info(dir.path());
assert!(result.is_err(), "should fail when manifest is missing");
}
#[test]
fn test_compile_resources_pri() {
let tools = locate_sdk_tools().expect("locate SDK");
if tools.makepri.is_none() {
eprintln!("Skipping test_compile_resources_pri: makepri.exe not found");
return;
}
let content_dir = tempdir().expect("create content dir");
create_minimal_appx_content(content_dir.path());
let pri = compile_resources_pri(
&tools,
&PriOptions {
appx_content_dir: content_dir.path(),
default_language: "en-us",
target_os_version: "10.0.0",
keep_priconfig: false,
overwrite: true,
makepri_override: None,
},
)
.expect("compile resources.pri");
assert!(pri.exists(), "resources.pri should be generated");
assert!(
!content_dir.path().join("priconfig.xml").exists(),
"priconfig.xml should be cleaned up by default"
);
}
#[test]
fn test_read_manifest_invalid_xml() {
let dir = tempdir().expect("create temp dir");
std::fs::write(dir.path().join("AppxManifest.xml"), "not valid xml <><>").expect("write");
let result = read_manifest_info(dir.path());
assert!(result.is_err(), "should fail for invalid XML");
}
#[test]
fn test_read_manifest_missing_identity() {
let dir = tempdir().expect("create temp dir");
let manifest = r#"<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10">
<Properties>
<DisplayName>TestApp</DisplayName>
</Properties>
</Package>"#;
std::fs::write(dir.path().join("AppxManifest.xml"), manifest).expect("write");
let result = read_manifest_info(dir.path());
assert!(
result.is_err(),
"should fail when Identity element is missing"
);
}
#[test]
fn test_read_manifest_missing_version() {
let dir = tempdir().expect("create temp dir");
let manifest = r#"<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10">
<Identity Name="TestCompany.TestApp" Publisher="CN=Test"/>
<Properties>
<DisplayName>TestApp</DisplayName>
</Properties>
</Package>"#;
std::fs::write(dir.path().join("AppxManifest.xml"), manifest).expect("write");
let result = read_manifest_info(dir.path());
assert!(
result.is_err(),
"should fail when Version attribute is missing"
);
}
#[test]
fn test_validate_msix_and_bundle() {
let tools = locate_sdk_tools().expect("locate SDK");
assert!(tools.appcert.is_some(), "WACK not installed");
let x64_dir = tempdir().expect("create x64 dir");
let arm64_dir = tempdir().expect("create arm64 dir");
let out_dir = tempdir().expect("create out dir");
create_appx_content_for_arch(x64_dir.path(), "x64");
create_appx_content_for_arch(arm64_dir.path(), "arm64");
if tools.makepri.is_some() {
compile_resources_pri(
&tools,
&PriOptions {
appx_content_dir: x64_dir.path(),
default_language: "en-us",
target_os_version: "10.0.0",
keep_priconfig: false,
overwrite: true,
makepri_override: None,
},
)
.expect("compile x64 resources.pri");
compile_resources_pri(
&tools,
&PriOptions {
appx_content_dir: arm64_dir.path(),
default_language: "en-us",
target_os_version: "10.0.0",
keep_priconfig: false,
overwrite: true,
makepri_override: None,
},
)
.expect("compile arm64 resources.pri");
}
let info = read_manifest_info(x64_dir.path()).expect("read manifest");
let msix_x64 =
pack_arch(&tools, x64_dir.path(), out_dir.path(), &info, "x64", false).expect("pack x64");
let msix_arm64 = pack_arch(
&tools,
arm64_dir.path(),
out_dir.path(),
&info,
"arm64",
false,
)
.expect("pack arm64");
validate_package(&tools, &msix_x64).expect("validate x64 msix should pass");
let built = vec![
("x64".to_string(), msix_x64),
("arm64".to_string(), msix_arm64),
];
let bundle = build_bundle(&tools, out_dir.path(), &built, &info, false).expect("build bundle");
validate_package(&tools, &bundle).expect("validate multi-arch bundle should pass");
}
#[test]
fn test_pack_arch_overwrite_false_fails_when_exists() {
let tools = locate_sdk_tools().expect("locate SDK");
let content_dir = tempdir().expect("create content dir");
let out_dir = tempdir().expect("create out dir");
create_minimal_appx_content(content_dir.path());
let info = read_manifest_info(content_dir.path()).expect("read manifest");
let msix = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
false,
)
.expect("first pack");
assert!(msix.exists());
let result = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
false,
);
assert!(
result.is_err(),
"pack should fail when file exists and overwrite=false"
);
}
#[test]
fn test_pack_arch_overwrite_true_succeeds_when_exists() {
let tools = locate_sdk_tools().expect("locate SDK");
let content_dir = tempdir().expect("create content dir");
let out_dir = tempdir().expect("create out dir");
create_minimal_appx_content(content_dir.path());
let info = read_manifest_info(content_dir.path()).expect("read manifest");
let msix1 = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
false,
)
.expect("first pack");
assert!(msix1.exists());
let msix2 = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
true,
)
.expect("second pack with overwrite");
assert!(msix2.exists());
assert_eq!(msix1, msix2, "should produce same path");
}
#[test]
fn test_build_bundle_overwrite_false_fails_when_exists() {
let tools = locate_sdk_tools().expect("locate SDK");
let content_dir = tempdir().expect("create content dir");
let out_dir = tempdir().expect("create out dir");
create_minimal_appx_content(content_dir.path());
let info = read_manifest_info(content_dir.path()).expect("read manifest");
let msix = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
false,
)
.expect("pack");
let built = vec![("x64".to_string(), msix)];
let bundle = build_bundle(&tools, out_dir.path(), &built, &info, false).expect("first bundle");
assert!(bundle.exists());
let result = build_bundle(&tools, out_dir.path(), &built, &info, false);
assert!(
result.is_err(),
"bundle should fail when file exists and overwrite=false"
);
}
#[test]
fn test_build_bundle_overwrite_true_succeeds_when_exists() {
let tools = locate_sdk_tools().expect("locate SDK");
let content_dir = tempdir().expect("create content dir");
let out_dir = tempdir().expect("create out dir");
create_minimal_appx_content(content_dir.path());
let info = read_manifest_info(content_dir.path()).expect("read manifest");
let msix = pack_arch(
&tools,
content_dir.path(),
out_dir.path(),
&info,
"x64",
false,
)
.expect("pack");
let built = vec![("x64".to_string(), msix)];
let bundle1 = build_bundle(&tools, out_dir.path(), &built, &info, false).expect("first bundle");
assert!(bundle1.exists());
let bundle2 = build_bundle(&tools, out_dir.path(), &built, &info, true)
.expect("second bundle with overwrite");
assert!(bundle2.exists());
assert_eq!(bundle1, bundle2, "should produce same path");
}
fn setup_test_certificate(dir: &Path) -> (std::path::PathBuf, std::path::PathBuf) {
let pfx_path = dir.join("APPX_TEST_ROOT.pfx");
let cer_path = dir.join("APPX_TEST_ROOT.cer");
let script = format!(
r#"$cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=Test" -CertStoreLocation Cert:\CurrentUser\My
$pwd = ConvertTo-SecureString -String "test" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath "{}" -Password $pwd
Export-Certificate -Cert $cert -FilePath "{}""#,
pfx_path.display(),
cer_path.display()
);
let status = std::process::Command::new("pwsh")
.args(["-Command", &script])
.status()
.expect("Failed to run pwsh");
assert!(status.success(), "Failed to generate test certificate");
assert!(pfx_path.exists(), "PFX file should exist");
assert!(cer_path.exists(), "CER file should exist");
(pfx_path, cer_path)
}
#[test]
fn test_sign_and_verify() {
let tools = locate_sdk_tools().expect("locate SDK");
let temp = tempdir().expect("create temp dir");
let (pfx_path, cer_path) = setup_test_certificate(temp.path());
let add_status = std::process::Command::new("certutil")
.args(["-addstore", "root"])
.arg(&cer_path)
.status()
.expect("Failed to run certutil");
assert!(add_status.success(), "Failed to add cert to root store");
let content_dir = temp.path().join("content");
std::fs::create_dir_all(&content_dir).expect("create content dir");
create_minimal_appx_content(&content_dir);
let info = read_manifest_info(&content_dir).expect("read manifest");
let msix_path = pack_arch(&tools, &content_dir, temp.path(), &info, "x64", true).expect("pack");
let sign_opts = SignOptions {
artifact: &msix_path,
certificate: CertificateSource::Pfx {
path: &pfx_path,
password: Some("test"),
},
sip_dll: None,
timestamp_url: None,
rfc3161: false,
signtool_override: None,
};
sign_artifact(&tools, &sign_opts).expect("sign artifact");
verify_signature(&tools, &msix_path).expect("verify signature");
let _ = std::process::Command::new("certutil")
.args(["-delstore", "root", "Test"])
.status();
}