diff --git a/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/SharedFragmentModelNamingTests.cs b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/SharedFragmentModelNamingTests.cs
new file mode 100644
index 00000000000..d75ea9e0f86
--- /dev/null
+++ b/src/StrawberryShake/CodeGeneration/test/CodeGeneration.CSharp.Tests/SharedFragmentModelNamingTests.cs
@@ -0,0 +1,240 @@
+namespace StrawberryShake.CodeGeneration.CSharp;
+
+///
+/// Characterizing tests for ChilliCream/graphql-platform#6873.
+/// The same named fragment spread across two operations currently generates
+/// two separate, divergently named per-operation models instead of a single
+/// shared model named after the fragment. These tests lock in the current
+/// behaviour so that a future fix has a clear, intentional target to change.
+///
+public class SharedFragmentModelNamingTests
+{
+ private const string Schema =
+ "type Query { student(id: Int!): Student students: [Student] } "
+ + "type Student { id: ID! name: String age: Int school: School } "
+ + "type School { id: ID! name: String }";
+
+ private const string Extensions = "extend schema @key(fields: \"id\")";
+
+ private const string Operations =
+ """
+ query GetStudentById($id: Int!) {
+ student(id: $id) {
+ ...MyStudent
+ }
+ }
+
+ query GetStudents {
+ students {
+ ...MyStudent
+ }
+ }
+
+ fragment MyStudent on Student {
+ id
+ name
+ age
+ school {
+ ...MySchool
+ }
+ }
+
+ fragment MySchool on School {
+ id
+ name
+ }
+ """;
+
+ [Fact]
+ public void SameFragmentInTwoOperations_Should_GenerateDivergentPerOperationStudentModels_When_Spread()
+ {
+ // arrange
+ var source = GenerateClientSource();
+
+ // act
+ var hasGetStudentByIdStudentClass =
+ source.Contains("class GetStudentById_Student_Student");
+ var hasGetStudentsStudentClass =
+ source.Contains("class GetStudents_Students_Student");
+ var hasSharedFragmentClass =
+ ContainsTypeDeclaration(source, "class", "MyStudent");
+
+ // assert
+ // Documents the gap: the same `MyStudent` fragment yields two separate,
+ // operation-scoped data classes and NO single shared `MyStudent` class.
+ Assert.True(
+ hasGetStudentByIdStudentClass,
+ "Expected the operation-scoped class 'GetStudentById_Student_Student'.");
+ Assert.True(
+ hasGetStudentsStudentClass,
+ "Expected the operation-scoped class 'GetStudents_Students_Student'.");
+ Assert.False(
+ hasSharedFragmentClass,
+ "Today no single shared 'MyStudent' data class is generated; "
+ + "if this fails the shared-fragment behaviour may have been fixed.");
+ }
+
+ [Fact]
+ public void SameFragmentInTwoOperations_Should_GenerateDivergentNestedSchoolModels_When_Spread()
+ {
+ // arrange
+ var source = GenerateClientSource();
+
+ // act
+ var hasGetStudentByIdSchoolClass =
+ source.Contains("class GetStudentById_Student_School_School");
+ var hasGetStudentsSchoolClass =
+ source.Contains("class GetStudents_Students_School_School");
+ var hasSharedFragmentClass =
+ ContainsTypeDeclaration(source, "class", "MySchool");
+
+ // assert
+ // The nested `MySchool` fragment also diverges into two operation-scoped
+ // data classes with no single shared `MySchool` class.
+ Assert.True(
+ hasGetStudentByIdSchoolClass,
+ "Expected the operation-scoped class 'GetStudentById_Student_School_School'.");
+ Assert.True(
+ hasGetStudentsSchoolClass,
+ "Expected the operation-scoped class 'GetStudents_Students_School_School'.");
+ Assert.False(
+ hasSharedFragmentClass,
+ "Today no single shared 'MySchool' data class is generated; "
+ + "if this fails the shared-fragment behaviour may have been fixed.");
+ }
+
+ [Fact]
+ public void SameFragmentInTwoOperations_Should_GenerateSingleSharedFragmentInterfaces_When_Spread()
+ {
+ // arrange
+ var source = GenerateClientSource();
+
+ // act
+ var myStudentInterfaceCount =
+ CountTypeDeclarations(source, "interface", "IMyStudent");
+ var mySchoolInterfaceCount =
+ CountTypeDeclarations(source, "interface", "IMySchool");
+
+ // assert
+ // The fragment INTERFACES are already shared (one each), which is the half
+ // of #6873 that works. The data classes that implement them are not.
+ Assert.Equal(1, myStudentInterfaceCount);
+ Assert.Equal(1, mySchoolInterfaceCount);
+ }
+
+ [Fact]
+ public void SameFragmentInTwoOperations_Should_TypeNestedSchoolPropertyByPerOperationInterface_When_Spread()
+ {
+ // arrange
+ var source = GenerateClientSource();
+
+ // act
+ var perOperationSchoolPropertyCount = CountOccurrences(
+ source,
+ "IGetStudentById_Student_School? School { get; }");
+ var sharedFragmentSchoolPropertyCount = CountOccurrences(
+ source,
+ "IMySchool? School { get; }");
+
+ // assert
+ // The student model's `school` property is typed by an operation-scoped
+ // interface (IGetStudentById_Student_School) rather than the nested
+ // fragment interface (IMySchool). This is the "randomly assigned type"
+ // behaviour described in #6873.
+ Assert.True(
+ perOperationSchoolPropertyCount > 0,
+ "Expected at least one 'school' property typed by a per-operation interface.");
+ Assert.Equal(0, sharedFragmentSchoolPropertyCount);
+ }
+
+ private static string GenerateClientSource()
+ {
+ // CSharpGenerator.Generate(IEnumerable) treats its arguments as
+ // file paths, so the GraphQL documents are written to a temporary folder.
+ var dir = Path.Combine(
+ Path.GetTempPath(),
+ "ss_frag_naming_" + Guid.NewGuid().ToString("N"));
+ Directory.CreateDirectory(dir);
+
+ try
+ {
+ var schemaFile = Path.Combine(dir, "schema.graphql");
+ var extensionsFile = Path.Combine(dir, "schema.extensions.graphql");
+ var operationsFile = Path.Combine(dir, "operations.graphql");
+
+ File.WriteAllText(schemaFile, Schema);
+ File.WriteAllText(extensionsFile, Extensions);
+ File.WriteAllText(operationsFile, Operations);
+
+ var result = CSharpGenerator.Generate(
+ [schemaFile, extensionsFile, operationsFile],
+ new CSharpGeneratorSettings
+ {
+ Namespace = "Foo.Bar",
+ ClientName = "FooClient",
+ AccessModifier = AccessModifier.Public
+ });
+
+ Assert.False(
+ result.Errors.Any(),
+ "It is expected that the result has no generator errors!");
+
+ return string.Join(
+ "\n",
+ result.Documents
+ .Where(d => d.Kind == SourceDocumentKind.CSharp)
+ .Select(d => d.SourceText));
+ }
+ finally
+ {
+ Directory.Delete(dir, recursive: true);
+ }
+ }
+
+ private static bool ContainsTypeDeclaration(string source, string keyword, string typeName) =>
+ CountTypeDeclarations(source, keyword, typeName) > 0;
+
+ private static int CountTypeDeclarations(string source, string keyword, string typeName)
+ {
+ // Matches " " only when is a whole token,
+ // so "interface IMyStudent" does not match "interface IMyStudentFoo".
+ var count = 0;
+
+ foreach (var rawLine in source.Split('\n'))
+ {
+ var line = rawLine.Trim();
+ var marker = keyword + " " + typeName;
+ var index = line.IndexOf(marker, StringComparison.Ordinal);
+
+ if (index < 0)
+ {
+ continue;
+ }
+
+ var afterIndex = index + marker.Length;
+
+ if (afterIndex >= line.Length || !IsIdentifierChar(line[afterIndex]))
+ {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ private static int CountOccurrences(string source, string value)
+ {
+ var count = 0;
+ var index = 0;
+
+ while ((index = source.IndexOf(value, index, StringComparison.Ordinal)) >= 0)
+ {
+ count++;
+ index += value.Length;
+ }
+
+ return count;
+ }
+
+ private static bool IsIdentifierChar(char c) => char.IsLetterOrDigit(c) || c == '_';
+}