using System.Reflection; using Morph.AiKit.Media; using Xunit; namespace Morph.AiKit.Tests; /// /// O canario da REGRA DURA de ai-agents/media-pipeline.md: /// nunca gere midia dentro de um turno de conversa. /// /// /// /// A regra nao e presa por prosa — e presa por ACESSIBILIDADE. O tipo que produz bytes e /// internal ao assembly; um handler de mensagem no projeto consumidor nao consegue /// nomea-lo, e o codigo nao compila. Este arquivo e o que mantem essa afirmacao verdadeira /// depois de qualquer refatoracao. /// /// /// A mutacao foi rodada (T10): tornar IMediaProvider publico derruba /// nomeando o defeito. Um canario que /// ninguem viu morder pode ser o canario errado. /// /// /// A leitura e por reflexao sobre a assembly, e nao por grep no fonte: o /// modificador que importa e o que sobreviveu a compilacao. Um grep por /// "internal interface" mediria o texto, e o texto e uma copia do objeto. /// /// public sealed class MediaAccessibilityTests { private static readonly Assembly Kit = typeof(MediaJob).Assembly; /// Os nomes que produzem, ou orquestram a producao de, bytes pagos. public static TheoryData ByteProducingTypes() => [ "Morph.AiKit.Media.IMediaProvider", "Morph.AiKit.Media.Providers.OpenAIImageProvider", "Morph.AiKit.Media.Providers.OpenAIImageGenerator", "Morph.AiKit.Media.Providers.GoogleImageProvider", "Morph.AiKit.Media.Providers.VeoVideoProvider", "Morph.AiKit.Media.MediaWorker", "Morph.AiKit.Media.MediaGenerator", "Morph.AiKit.Media.MediaProviderCatalog", "Morph.AiKit.Media.MediaArtifact", ]; /// /// A asserção literal do plano: /// typeof(MediaJob).Assembly.GetType("Morph.AiKit.Media.IMediaProvider")!.IsPublic == false. /// [Fact] public void TheTypeThatProducesBytesIsNotPublic() { var provider = Kit.GetType("Morph.AiKit.Media.IMediaProvider"); Assert.NotNull(provider); Assert.False( provider.IsPublic, "IMediaProvider virou publico: um handler de mensagem passa a conseguir nomear o tipo que " + "produz bytes, e a regra dura de ai-agents/media-pipeline.md deixa de ser presa pelo " + "compilador. E prosa outra vez."); } /// Uma rodada por tipo: nenhum deles pode vazar para fora do assembly. [Theory] [MemberData(nameof(ByteProducingTypes))] public void NoByteProducingTypeIsVisibleOutsideTheAssembly(string fullName) { var type = Kit.GetType(fullName); Assert.NotNull(type); Assert.False(type.IsPublic, $"{fullName} nao pode ser publico."); Assert.False(type.IsNestedPublic, $"{fullName} nao pode ser aninhado publico."); } /// /// O outro lado da mesma afirmacao: a superficie publica EXISTE, e ela nunca devolve /// bytes. Sem esta metade, tornar tudo internal passaria — e um subsistema inalcancavel /// tambem esta errado. /// [Fact] public void ThePublicEntryPointExistsAndNeverReturnsBytes() { var generator = Kit.GetType("Morph.AiKit.Media.IMediaGenerator"); Assert.NotNull(generator); Assert.True(generator.IsPublic, "IMediaGenerator precisa ser publico — e a unica porta de entrada."); foreach (var method in generator.GetMethods()) { var returned = method.ReturnType.IsGenericType ? method.ReturnType.GetGenericArguments()[0] : method.ReturnType; var payload = returned.IsGenericType ? returned.GetGenericArguments()[0] : returned; Assert.True( payload == typeof(MediaJobId) || payload == typeof(MediaJob), $"IMediaGenerator.{method.Name} devolve '{payload.Name}'. A superficie publica devolve id " + "de job ou o job — nunca bytes, stream, BinaryData ou URL de provider."); } } /// /// — o registro que CARREGA os bytes — nao pode aparecer em /// nenhuma assinatura publica do subsistema. Ele existe entre dois tipos internal. /// [Fact] public void TheArtifactNeverAppearsInAPublicSignature() { var artifact = Kit.GetType("Morph.AiKit.Media.MediaArtifact"); Assert.NotNull(artifact); var leaks = new List(); foreach (var type in Kit.GetExportedTypes().Where(t => t.Namespace?.StartsWith("Morph.AiKit.Media", StringComparison.Ordinal) == true)) { foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)) { if (Mentions(method.ReturnType, artifact) || method.GetParameters().Any(p => Mentions(p.ParameterType, artifact))) { leaks.Add($"{type.FullName}.{method.Name}"); } } } Assert.Empty(leaks); } private static bool Mentions(Type candidate, Type target) => candidate == target || (candidate.IsGenericType && candidate.GetGenericArguments().Any(a => Mentions(a, target))); }