Skip to content

Commit 1c4cb0a

Browse files
committed
more tests and improvemtns
1 parent 5645fa8 commit 1c4cb0a

29 files changed

+1461
-835
lines changed

ManagedCode.MimeTypes.Generator/ManagedCode.MimeTypes.Generator.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
<PrivateAssets>all</PrivateAssets>
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
</PackageReference>
15-
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="all" />
1615
</ItemGroup>
1716

1817
<PropertyGroup>

ManagedCode.MimeTypes.Generator/MimeTypeSourceGenerator.cs

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,25 @@
66
using System.Collections.Generic;
77
using System.Diagnostics;
88
using System.Linq;
9-
using Newtonsoft.Json.Linq;
9+
using System.Text.Json;
1010

1111
namespace ManagedCode.MimeTypes.Generator;
1212

13+
/// <summary>
14+
/// Emits source that bootstraps MIME mappings and exposes typed constants for each extension.
15+
/// </summary>
1316
[Generator]
1417
public class MimeTypeSourceGenerator : ISourceGenerator
1518
{
19+
private static readonly DiagnosticDescriptor MimeTypesLoadedDiagnostic = new(
20+
"MIME002",
21+
"MimeTypes loaded",
22+
"Successfully loaded {0} mime types",
23+
"MimeTypes",
24+
DiagnosticSeverity.Info,
25+
isEnabledByDefault: true);
26+
27+
/// <inheritdoc />
1628
public void Initialize(GeneratorInitializationContext context)
1729
{
1830
#if DEBUG
@@ -23,6 +35,7 @@ public void Initialize(GeneratorInitializationContext context)
2335
#endif
2436
}
2537

38+
/// <inheritdoc />
2639
public void Execute(GeneratorExecutionContext context)
2740
{
2841
try
@@ -45,28 +58,22 @@ public void Execute(GeneratorExecutionContext context)
4558
return;
4659
}
4760

48-
var mime = JObject.Parse(File.ReadAllText(mimeTypesPath));
49-
var properties = mime.Properties().ToList();
50-
51-
context.ReportDiagnostic(Diagnostic.Create(
52-
new DiagnosticDescriptor(
53-
"MIME002",
54-
"MimeTypes loaded",
55-
"Successfully loaded {0} mime types",
56-
"MimeTypes",
57-
DiagnosticSeverity.Info,
58-
true),
59-
Location.None,
60-
properties.Count));
61+
var json = File.ReadAllBytes(mimeTypesPath);
62+
using var document = JsonDocument.Parse(json);
6163

6264
StringBuilder defineDictionaryBuilder = new();
6365
StringBuilder propertyBuilder = new();
64-
Dictionary<string, string> types = new Dictionary<string, string>();
66+
Dictionary<string, string> types = new(StringComparer.OrdinalIgnoreCase);
6567

66-
foreach (var item in properties)
68+
foreach (var item in document.RootElement.EnumerateObject())
6769
{
6870
var extension = item.Name.Trim();
69-
var mimeValue = item.Value.ToString()?.Trim() ?? string.Empty;
71+
var mimeValue = item.Value.GetString()?.Trim() ?? string.Empty;
72+
73+
if (extension.Length == 0 || mimeValue.Length == 0)
74+
{
75+
continue;
76+
}
7077

7178
defineDictionaryBuilder.AppendLine($"RegisterMimeTypeInternal(\"{Escape(extension)}\", \"{Escape(mimeValue)}\");");
7279
types[ParseKey(extension)] = mimeValue;
@@ -76,6 +83,8 @@ public void Execute(GeneratorExecutionContext context)
7683
{
7784
propertyBuilder.AppendLine($"public static string {item.Key} => \"{Escape(item.Value)}\";");
7885
}
86+
87+
context.ReportDiagnostic(Diagnostic.Create(MimeTypesLoadedDiagnostic, Location.None, types.Count));
7988

8089
context.AddSource("MimeHelper.Properties.cs", SourceText.From(@$"
8190
namespace ManagedCode.MimeTypes
@@ -146,5 +155,3 @@ private static string Escape(string value)
146155
.Replace("\"", "\\\"");
147156
}
148157
}
149-
150-

ManagedCode.MimeTypes.Tests/ContentDetectionTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,22 @@ public void DocxHeader_ShouldBeDetected()
5959
var docxBytes = Combine(
6060
new byte[] { 0x50, 0x4B, 0x03, 0x04 },
6161
Encoding.ASCII.GetBytes("word/document.xml"));
62-
Detect(docxBytes).ShouldBe("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
62+
Detect(docxBytes).ShouldBe(MimeHelper.DOCX);
6363
}
6464

6565
[Fact]
6666
public void ShortStream_ShouldReturnDefault()
6767
{
6868
using var stream = new MemoryStream(new byte[] { 0x01, 0x02 });
69-
MimeHelper.GetMimeTypeByContent(stream).ShouldBe("application/octet-stream");
69+
MimeHelper.GetMimeTypeByContent(stream).ShouldBe(MimeHelper.BIN);
7070
stream.Position.ShouldBe(0);
7171
}
7272

7373
[Fact]
7474
public void EmptyStream_ShouldReturnDefault()
7575
{
7676
using var stream = new MemoryStream();
77-
MimeHelper.GetMimeTypeByContent(stream).ShouldBe("application/octet-stream");
77+
MimeHelper.GetMimeTypeByContent(stream).ShouldBe(MimeHelper.BIN);
7878
}
7979

8080
[Fact]
@@ -85,7 +85,7 @@ public void FilePathOverload_ShouldDetect()
8585
try
8686
{
8787
File.WriteAllBytes(tempFile, pdfBytes);
88-
MimeHelper.GetMimeTypeByContent(tempFile).ShouldBe("application/pdf");
88+
MimeHelper.GetMimeTypeByContent(tempFile).ShouldBe(MimeHelper.PDF);
8989
}
9090
finally
9191
{

ManagedCode.MimeTypes.Tests/GeneratorTests.cs

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,71 +8,76 @@ public class GeneratorTests
88
[Fact]
99
public void ExtensionsTest()
1010
{
11-
MimeHelper.GetMimeType("somefile.pdf").ShouldBe("application/pdf");
12-
MimeHelper.GetMimeType("pdf").ShouldBe("application/pdf");
13-
MimeHelper.GetMimeType(".gz").ShouldBe("application/gzip");
14-
MimeHelper.GetMimeType("word.docx").ShouldBe("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
15-
MimeHelper.GetMimeType("C:\\\\users\\file.txt").ShouldBe("text/plain");
16-
MimeHelper.GetMimeType("https://cdn.example.com/assets/image.png?version=1").ShouldBe("image/png");
17-
MimeHelper.GetMimeType("ARCHIVE.TAR.GZ").ShouldBe("application/gzip");
18-
MimeHelper.GetMimeType("module.d.ts").ShouldBe("application/typescript");
11+
MimeHelper.GetMimeType("somefile.pdf").ShouldBe(MimeHelper.PDF);
12+
MimeHelper.GetMimeType("pdf").ShouldBe(MimeHelper.PDF);
13+
MimeHelper.GetMimeType(".gz").ShouldBe(MimeHelper.GZ);
14+
MimeHelper.GetMimeType("word.docx").ShouldBe(MimeHelper.DOCX);
15+
MimeHelper.GetMimeType("C:\\\\users\\file.txt").ShouldBe(MimeHelper.TXT);
16+
MimeHelper.GetMimeType("https://cdn.example.com/assets/image.png?version=1").ShouldBe(MimeHelper.PNG);
17+
MimeHelper.GetMimeType("ARCHIVE.TAR.GZ").ShouldBe(MimeHelper.GZ);
18+
MimeHelper.GetMimeType("module.d.ts").ShouldBe(MimeHelper.D_TS);
1919
}
2020

2121
[Fact]
2222
public void EmptyExtensionsTest()
2323
{
24-
MimeHelper.GetMimeType("").ShouldBe("application/octet-stream");
25-
MimeHelper.GetMimeType(" ").ShouldBe("application/octet-stream");
26-
MimeHelper.GetMimeType(null as string).ShouldBe("application/octet-stream");
24+
MimeHelper.GetMimeType("").ShouldBe(MimeHelper.BIN);
25+
MimeHelper.GetMimeType(" ").ShouldBe(MimeHelper.BIN);
26+
MimeHelper.GetMimeType(null as string).ShouldBe(MimeHelper.BIN);
2727
}
2828

2929
[Fact]
3030
public void GeneratedPropertiesTest()
3131
{
3232
// Test static properties generated from mimeTypes.json
33-
MimeHelper.PDF.ShouldBe("application/pdf");
34-
MimeHelper.DOCX.ShouldBe("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
35-
MimeHelper.PNG.ShouldBe("image/png");
36-
MimeHelper.MP4.ShouldBe("video/mp4");
37-
MimeHelper._7Z.ShouldBe("application/x-7z-compressed");
38-
MimeHelper.EVENT_STREAM.ShouldBe("text/event-stream");
33+
const string eventStreamMime = "text/event-stream";
34+
MimeHelper.PDF.ShouldBe(MimeHelper.GetMimeType(".pdf"));
35+
MimeHelper.DOCX.ShouldBe(MimeHelper.GetMimeType(".docx"));
36+
MimeHelper.PNG.ShouldBe(MimeHelper.GetMimeType(".png"));
37+
MimeHelper.MP4.ShouldBe(MimeHelper.GetMimeType(".mp4"));
38+
MimeHelper._7Z.ShouldBe(MimeHelper.GetMimeType(".7z"));
39+
MimeHelper.EVENT_STREAM.ShouldBe(eventStreamMime);
3940
}
4041

4142
[Fact]
4243
public void GeneratedDictionaryTest()
4344
{
4445
// Test if dictionary is properly initialized
45-
MimeHelper.GetMimeType(".pdf").ShouldBe("application/pdf");
46-
MimeHelper.GetMimeType(".docx").ShouldBe("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
47-
MimeHelper.GetMimeType(".7z").ShouldBe("application/x-7z-compressed");
46+
MimeHelper.GetMimeType(".pdf").ShouldBe(MimeHelper.PDF);
47+
MimeHelper.GetMimeType(".docx").ShouldBe(MimeHelper.DOCX);
48+
MimeHelper.GetMimeType(".7z").ShouldBe(MimeHelper._7Z);
4849
}
4950

5051
[Fact]
5152
public void GetExtensionsShouldReturnKnownExtensions()
5253
{
53-
var jpegExtensions = MimeHelper.GetExtensions("image/jpeg");
54+
var jpegExtensions = MimeHelper.GetExtensions(MimeHelper.JPG);
5455
jpegExtensions.ShouldContain(".jpg");
5556
jpegExtensions.ShouldContain(".jpeg");
5657
jpegExtensions.ShouldContain(".jpe");
5758

58-
MimeHelper.TryGetExtensions("application/x-unknown", out _).ShouldBeFalse();
59+
const string unknownMime = "application/x-unknown";
60+
MimeHelper.TryGetExtensions(unknownMime, out _).ShouldBeFalse();
5961
}
6062

6163
[Fact]
6264
public void RuntimeRegistrationShouldUpdateLookups()
6365
{
66+
const string extension = "customext";
67+
const string mime = "application/x-custom";
68+
6469
try
6570
{
66-
MimeHelper.RegisterMimeType("customext", "application/x-custom");
67-
MimeHelper.GetMimeType("file.customext").ShouldBe("application/x-custom");
71+
MimeHelper.RegisterMimeType(extension, mime);
72+
MimeHelper.GetMimeType("file.customext").ShouldBe(mime);
6873

69-
MimeHelper.TryGetExtensions("application/x-custom", out var extensions).ShouldBeTrue();
74+
MimeHelper.TryGetExtensions(mime, out var extensions).ShouldBeTrue();
7075
extensions.ShouldContain(".customext");
7176
}
7277
finally
7378
{
74-
MimeHelper.UnregisterMimeType("customext").ShouldBeTrue();
75-
MimeHelper.GetMimeType("customext").ShouldBe("application/octet-stream");
79+
MimeHelper.UnregisterMimeType(extension).ShouldBeTrue();
80+
MimeHelper.GetMimeType(extension).ShouldBe(MimeHelper.BIN);
7681
}
7782
}
7883
}

ManagedCode.MimeTypes.Tests/MimeCategoryTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,29 @@ public void PresentationMimeTypes_ShouldBePresentation(string mime)
118118

119119
[Theory]
120120
[InlineData("application/javascript")]
121+
[InlineData("application/x-javascript")]
121122
[InlineData("text/x-php")]
123+
[InlineData("application/x-httpd-php")]
122124
[InlineData("application/x-sh")]
125+
[InlineData("text/coffeescript")]
126+
[InlineData("application/vnd.google-apps.script")]
127+
[InlineData("application/x-powershell")]
123128
public void ScriptMimeTypes_ShouldBeScript(string mime)
124129
{
125130
MimeHelper.GetMimeCategory(mime).ShouldBe(MimeTypeCategory.Script);
126131
MimeHelper.IsScript(mime).ShouldBeTrue();
127132
}
128133

134+
[Theory]
135+
[InlineData("application/postscript")]
136+
[InlineData("application/vnd.cups-postscript")]
137+
[InlineData("application/x-font-ghostscript")]
138+
public void PostScriptMimeTypes_ShouldNotBeScript(string mime)
139+
{
140+
MimeHelper.IsScript(mime).ShouldBeFalse();
141+
MimeHelper.GetMimeCategory(mime).ShouldNotBe(MimeTypeCategory.Script);
142+
}
143+
129144
[Theory]
130145
[InlineData("application/octet-stream")]
131146
[InlineData("application/x-binary")]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using Shouldly;
3+
using Xunit;
4+
5+
namespace ManagedCode.MimeTypes.Tests;
6+
7+
/// <summary>
8+
/// Verifies individual behaviours of <see cref="MimeHelper"/> that modify global state.
9+
/// </summary>
10+
[Collection(MimeHelperMutableStateCollection.Name)]
11+
public class MimeHelperBehaviourTests
12+
{
13+
[Fact]
14+
public void DefaultMimeType_ShouldMatchGeneratedBin()
15+
{
16+
MimeHelper.DefaultMimeType.ShouldBe(MimeHelper.BIN);
17+
}
18+
19+
[Fact]
20+
public void SetDefaultMimeType_ShouldInfluenceFallbackLookup()
21+
{
22+
var original = MimeHelper.DefaultMimeType;
23+
const string customDefault = "application/x-custom-default";
24+
25+
try
26+
{
27+
MimeHelper.SetDefaultMimeType(customDefault);
28+
29+
MimeHelper.GetMimeType((string?)null).ShouldBe(customDefault);
30+
MimeHelper.GetMimeType(string.Empty).ShouldBe(customDefault);
31+
MimeHelper.GetMimeType(" ").ShouldBe(customDefault);
32+
}
33+
finally
34+
{
35+
MimeHelper.SetDefaultMimeType(original);
36+
}
37+
}
38+
39+
[Fact]
40+
public void SetDefaultMimeType_ShouldRejectInvalidValues()
41+
{
42+
Should.Throw<ArgumentException>(() => MimeHelper.SetDefaultMimeType(null!));
43+
Should.Throw<ArgumentException>(() => MimeHelper.SetDefaultMimeType(" "));
44+
}
45+
46+
[Fact]
47+
public void RegisterScriptMimeType_ShouldUpdateCaches()
48+
{
49+
const string extension = "myscript";
50+
const string mime = "application/x-shellscript";
51+
52+
try
53+
{
54+
MimeHelper.RegisterMimeType(extension, mime);
55+
56+
MimeHelper.GetMimeType(extension).ShouldBe(mime);
57+
MimeHelper.IsScript(mime).ShouldBeTrue();
58+
}
59+
finally
60+
{
61+
MimeHelper.UnregisterMimeType(extension);
62+
}
63+
}
64+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Shouldly;
5+
using Xunit;
6+
7+
namespace ManagedCode.MimeTypes.Tests;
8+
9+
/// <summary>
10+
/// Validates that the mutable portions of <see cref="MimeHelper"/> behave correctly under concurrent access.
11+
/// </summary>
12+
[Collection(MimeHelperMutableStateCollection.Name)]
13+
public class MimeHelperConcurrencyTests
14+
{
15+
[Fact]
16+
public void ConcurrentRegisterAndLookup_ShouldRemainThreadSafe()
17+
{
18+
var extensions = Enumerable.Range(0, 128)
19+
.Select(index => $"ext{index:D4}")
20+
.ToArray();
21+
22+
Parallel.ForEach(extensions, extension =>
23+
{
24+
MimeHelper.RegisterMimeType(extension, MimeHelper.TXT);
25+
});
26+
27+
try
28+
{
29+
Parallel.ForEach(extensions, extension =>
30+
{
31+
var mime = MimeHelper.GetMimeType(extension);
32+
mime.ShouldBe(MimeHelper.TXT);
33+
MimeHelper.IsText(mime).ShouldBeTrue();
34+
});
35+
}
36+
finally
37+
{
38+
foreach (var extension in extensions)
39+
{
40+
MimeHelper.UnregisterMimeType(extension);
41+
}
42+
}
43+
}
44+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name,value
2+
alpha,1
3+
beta,2
30 Bytes
Binary file not shown.
28 Bytes
Loading

0 commit comments

Comments
 (0)