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)));
}