Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test-dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- main
- user/asklar/dotnet
- dotnet
paths:
- 'dotnet/**'
- 'examples/**'
Expand Down
79 changes: 79 additions & 0 deletions dotnet/mcpb.Tests/CliPackFileValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,83 @@ public void Pack_AllFilesPresent_Succeeds()
Assert.Contains("demo@", stdout);
Assert.DoesNotContain("Missing", stderr);
}

[Fact]
public void Pack_MissingIconsFile_Fails()
{
var dir = CreateTempDir();
File.WriteAllText(Path.Combine(dir, "icon.png"), "fake");
File.WriteAllText(Path.Combine(dir, "server", "index.js"), "// js");
var manifest = BaseManifest();
manifest.ManifestVersion = "0.3";
manifest.Icons = new List<Mcpb.Core.McpbManifestIcon>
{
new() { Src = "icon-16.png", Size = "16x16" }
};
File.WriteAllText(Path.Combine(dir, "manifest.json"), JsonSerializer.Serialize(manifest, McpbJsonContext.WriteOptions));
var (code, _, stderr) = InvokeCli(dir, "pack", dir, "--no-discover");
Assert.NotEqual(0, code);
Assert.Contains("Missing icons[0] file", stderr);
}

[Fact]
public void Pack_IconsFilePresent_Succeeds()
{
var dir = CreateTempDir();
File.WriteAllText(Path.Combine(dir, "icon.png"), "fake");
File.WriteAllText(Path.Combine(dir, "icon-16.png"), "fake16");
File.WriteAllText(Path.Combine(dir, "server", "index.js"), "// js");
var manifest = BaseManifest();
manifest.ManifestVersion = "0.3";
manifest.Screenshots = null; // Remove screenshots requirement for this test
manifest.Icons = new List<Mcpb.Core.McpbManifestIcon>
{
new() { Src = "icon-16.png", Size = "16x16" }
};
File.WriteAllText(Path.Combine(dir, "manifest.json"), JsonSerializer.Serialize(manifest, McpbJsonContext.WriteOptions));
var (code, stdout, stderr) = InvokeCli(dir, "pack", dir, "--no-discover");
Assert.True(code == 0, $"Pack failed with code {code}. Stderr: {stderr}");
Assert.Contains("demo@", stdout);
}

[Fact]
public void Pack_MissingLocalizationResources_Fails()
{
var dir = CreateTempDir();
File.WriteAllText(Path.Combine(dir, "icon.png"), "fake");
File.WriteAllText(Path.Combine(dir, "server", "index.js"), "// js");
var manifest = BaseManifest();
manifest.ManifestVersion = "0.3";
manifest.Localization = new Mcpb.Core.McpbManifestLocalization
{
Resources = "locales/${locale}/messages.json",
DefaultLocale = "en-US"
};
File.WriteAllText(Path.Combine(dir, "manifest.json"), JsonSerializer.Serialize(manifest, McpbJsonContext.WriteOptions));
var (code, _, stderr) = InvokeCli(dir, "pack", dir, "--no-discover");
Assert.NotEqual(0, code);
Assert.Contains("Missing localization resources", stderr);
}

[Fact]
public void Pack_LocalizationResourcesPresent_Succeeds()
{
var dir = CreateTempDir();
File.WriteAllText(Path.Combine(dir, "icon.png"), "fake");
File.WriteAllText(Path.Combine(dir, "server", "index.js"), "// js");
Directory.CreateDirectory(Path.Combine(dir, "locales", "en-US"));
File.WriteAllText(Path.Combine(dir, "locales", "en-US", "messages.json"), "{}");
var manifest = BaseManifest();
manifest.ManifestVersion = "0.3";
manifest.Screenshots = null; // Remove screenshots requirement for this test
manifest.Localization = new Mcpb.Core.McpbManifestLocalization
{
Resources = "locales/${locale}/messages.json",
DefaultLocale = "en-US"
};
File.WriteAllText(Path.Combine(dir, "manifest.json"), JsonSerializer.Serialize(manifest, McpbJsonContext.WriteOptions));
var (code, stdout, stderr) = InvokeCli(dir, "pack", dir, "--no-discover");
Assert.True(code == 0, $"Pack failed with code {code}. Stderr: {stderr}");
Assert.Contains("demo@", stdout);
}
}
134 changes: 134 additions & 0 deletions dotnet/mcpb.Tests/ManifestValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,138 @@ public void PromptMissingText_ProducesWarning()
var warning = Assert.Single(issues, i => i.Path == "prompts[0].text");
Assert.Equal(ValidationSeverity.Warning, warning.Severity);
}

[Fact]
public void ValidLocalization_Passes()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
m.Localization = new McpbManifestLocalization
{
Resources = "locales/${locale}/messages.json",
DefaultLocale = "en-US"
};
var issues = ManifestValidator.Validate(m);
Assert.Empty(issues);
}

[Fact]
public void LocalizationWithDefaults_Passes()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
// Resources and DefaultLocale are optional with defaults
m.Localization = new McpbManifestLocalization
{
Resources = null, // defaults to "mcpb-resources/${locale}.json"
DefaultLocale = null // defaults to "en-US"
};
var issues = ManifestValidator.Validate(m);
Assert.Empty(issues);
}

[Fact]
public void LocalizationResourcesWithoutPlaceholder_Fails()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
m.Localization = new McpbManifestLocalization
{
Resources = "locales/messages.json",
DefaultLocale = "en-US"
};
var issues = ManifestValidator.Validate(m);
Assert.Contains(issues, i => i.Path == "localization.resources" && i.Message.Contains("placeholder"));
}

[Fact]
public void LocalizationEmptyObject_PassesWithDefaults()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
// Empty localization object should use defaults
m.Localization = new McpbManifestLocalization();
var issues = ManifestValidator.Validate(m);
Assert.Empty(issues);
}

[Fact]
public void LocalizationInvalidDefaultLocale_Fails()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
m.Localization = new McpbManifestLocalization
{
Resources = "locales/${locale}/messages.json",
DefaultLocale = "invalid locale"
};
var issues = ManifestValidator.Validate(m);
Assert.Contains(issues, i => i.Path == "localization.default_locale" && i.Message.Contains("BCP 47"));
}

[Fact]
public void ValidIcons_Passes()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
m.Icons = new List<McpbManifestIcon>
{
new() { Src = "icon-16.png", Size = "16x16" },
new() { Src = "icon-32.png", Size = "32x32", Theme = "light" }
};
var issues = ManifestValidator.Validate(m);
Assert.Empty(issues);
}

[Fact]
public void IconMissingSrc_Fails()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
m.Icons = new List<McpbManifestIcon>
{
new() { Src = "", Size = "16x16" }
};
var issues = ManifestValidator.Validate(m);
Assert.Contains(issues, i => i.Path == "icons[0].src");
}

[Fact]
public void IconMissingSize_Fails()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
m.Icons = new List<McpbManifestIcon>
{
new() { Src = "icon.png", Size = "" }
};
var issues = ManifestValidator.Validate(m);
Assert.Contains(issues, i => i.Path == "icons[0].size");
}

[Fact]
public void IconInvalidSizeFormat_Fails()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
m.Icons = new List<McpbManifestIcon>
{
new() { Src = "icon.png", Size = "16" }
};
var issues = ManifestValidator.Validate(m);
Assert.Contains(issues, i => i.Path == "icons[0].size" && i.Message.Contains("WIDTHxHEIGHT"));
}

[Fact]
public void IconEmptyTheme_Fails()
{
var m = BaseManifest();
m.ManifestVersion = "0.3";
m.Icons = new List<McpbManifestIcon>
{
new() { Src = "icon.png", Size = "16x16", Theme = "" }
};
var issues = ManifestValidator.Validate(m);
Assert.Contains(issues, i => i.Path == "icons[0].theme" && i.Message.Contains("empty"));
}
}
Loading