@polymorphism-tech/morph-spec 8.38.0 → 8.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/CLAUDE.md +20 -9
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +60 -43
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +4 -3
  8. package/framework/MORPH.md +10 -9
  9. package/framework/agents.json +43 -26
  10. package/framework/docs/CLI.md +7 -5
  11. package/framework/evals/rubrics/code-quality.md +4 -4
  12. package/framework/evals/rubrics/condensed.md +2 -2
  13. package/framework/hooks/README.md +1 -1
  14. package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
  15. package/framework/hooks/claude-code/core/worktree-create.js +22 -6
  16. package/framework/hooks/shared/git-paths.js +38 -5
  17. package/framework/mcp/registry.json +58 -66
  18. package/framework/rules/infrastructure-standards.md +0 -1
  19. package/framework/rules/morph-workflow.md +9 -4
  20. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  21. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  22. package/framework/skills/core/morph-apply/SKILL.md +59 -25
  23. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  24. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  25. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  26. package/framework/skills/core/morph-implement/SKILL.md +48 -24
  27. package/framework/skills/core/morph-media/SKILL.md +2 -2
  28. package/framework/skills/core/morph-plan/SKILL.md +10 -3
  29. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  30. package/framework/skills/core/morph-review/SKILL.md +1 -1
  31. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  32. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  33. package/framework/skills/core/morph-uiux/SKILL.md +7 -6
  34. package/framework/standards/PROVENANCE.json +4 -4
  35. package/framework/standards/STANDARDS.json +9 -9
  36. package/framework/standards/ai-agents/context-providers.md +16 -26
  37. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  38. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  39. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  40. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  41. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  42. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  43. package/framework/standards/architecture/code-comments.md +52 -16
  44. package/framework/standards/backend/dotnet/testing.md +4 -2
  45. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  46. package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
  47. package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
  48. package/framework/standards/frontend/nextjs/testing.md +4 -2
  49. package/framework/standards/integration/mcp/mcp-tools.md +64 -126
  50. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  51. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  52. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  53. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  54. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  55. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  56. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  57. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  58. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  59. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  60. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  61. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  62. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  63. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  64. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  65. package/framework/templates/code/dotnet/test.cs +2 -87
  66. package/framework/templates/code/typescript/contracts.ts +140 -167
  67. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  68. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  69. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  70. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  71. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  72. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  73. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  74. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  75. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  76. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  77. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  78. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  79. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  80. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  81. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  82. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  83. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  84. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  85. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  86. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  87. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  88. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  89. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  90. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  91. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  92. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  93. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  94. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  95. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  96. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  118. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  119. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  120. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  121. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  122. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  123. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  124. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  127. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  128. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  129. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  130. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  131. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  132. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  134. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  135. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  136. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  137. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  138. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  139. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  140. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  141. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  142. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  143. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  144. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  145. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  146. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  147. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  148. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  149. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  150. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  151. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  152. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  153. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  154. package/framework/templates/integrations/asaas-client.cs +332 -387
  155. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  156. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  157. package/framework/templates/project/validate.js +97 -120
  158. package/package.json +2 -2
  159. package/src/commands/dashboard/dashboard.js +80 -53
  160. package/src/commands/mcp/mcp-setup.js +37 -9
  161. package/src/commands/project/doctor.js +12 -6
  162. package/src/commands/project/init.js +6 -3
  163. package/src/commands/project/install-plugin-cmd.js +1 -1
  164. package/src/commands/project/update.js +5 -0
  165. package/src/commands/project/worktree.js +60 -4
  166. package/src/commands/state/gate-check.js +5 -0
  167. package/src/commands/tasks/dag.js +27 -4
  168. package/src/commands/validation/verify.js +22 -38
  169. package/src/dashboard/app/App.jsx +80 -16
  170. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  171. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  172. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  173. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  174. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  175. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  176. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  177. package/src/dashboard/app/components/Markdown.jsx +47 -0
  178. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  179. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  180. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  181. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  182. package/src/dashboard/app/hooks/usePref.js +38 -0
  183. package/src/dashboard/app/lib/format.js +69 -0
  184. package/src/dashboard/app/lib/phases.js +56 -0
  185. package/src/dashboard/app/lib/project.js +50 -0
  186. package/src/dashboard/app/pages/Activity.jsx +24 -0
  187. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  188. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  189. package/src/dashboard/app/pages/Features.jsx +400 -0
  190. package/src/dashboard/app/pages/Guide.jsx +146 -0
  191. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  192. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  193. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  194. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  195. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  196. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  197. package/src/dashboard/app/pages/Standards.jsx +407 -332
  198. package/src/dashboard/app/styles/index.css +187 -47
  199. package/src/dashboard/index.html +14 -12
  200. package/src/dashboard/package.json +32 -24
  201. package/src/dashboard/scanner/activity-scanner.js +351 -0
  202. package/src/dashboard/scanner/agents-scanner.js +59 -1
  203. package/src/dashboard/scanner/docs-scanner.js +59 -0
  204. package/src/dashboard/scanner/feature-status.js +10 -0
  205. package/src/dashboard/scanner/features-scanner.js +360 -0
  206. package/src/dashboard/scanner/index.js +14 -7
  207. package/src/dashboard/scanner/phases-scanner.js +14 -15
  208. package/src/dashboard/scanner/portfolio.js +153 -0
  209. package/src/dashboard/scanner/project-api.js +195 -0
  210. package/src/dashboard/scanner/rules-scanner.js +42 -37
  211. package/src/dashboard/scanner/skills-scanner.js +105 -43
  212. package/src/dashboard/scanner/standards-scanner.js +2 -0
  213. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  214. package/src/lib/detectors/claude-config-detector.js +2 -7
  215. package/src/lib/detectors/index.js +5 -1
  216. package/src/lib/evaluator-report.js +22 -0
  217. package/src/lib/generators/settings-generator.js +208 -209
  218. package/src/lib/installers/mcp-installer.js +724 -719
  219. package/src/lib/installers/memory-installer.js +1 -2
  220. package/src/lib/projects/registry.js +154 -0
  221. package/src/lib/risk-detector.js +13 -0
  222. package/src/lib/tasks/tasks-json-parser.js +21 -0
  223. package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
  224. package/src/lib/worktree/env-allocator.js +9 -3
  225. package/src/scripts/install-plugin.js +1 -1
  226. package/src/scripts/setup-infra.js +2 -4
  227. package/src/utils/agents-installer.js +108 -13
  228. package/src/utils/banner.js +1 -1
  229. package/src/commands/index.js +0 -13
  230. package/src/commands/project/checkpoint.js +0 -209
  231. package/src/core/state/phase-state-machine.js +0 -197
  232. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  233. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  234. package/src/dashboard/scanner/squads-scanner.js +0 -13
  235. package/src/dashboard/scanner/state-scanner.js +0 -85
  236. package/src/lib/generators/index.js +0 -10
  237. package/src/lib/hooks/hook-executor.js +0 -167
  238. package/src/lib/validators/checkpoint-persistence.js +0 -39
  239. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  240. package/src/lib/validators/shared/index.js +0 -12
  241. package/src/lib/validators/shared/issue-counter.js +0 -14
  242. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -5,15 +5,6 @@ using Xunit;
5
5
 
6
6
  namespace Morph.AiKit.Tests;
7
7
 
8
- /// <summary>
9
- /// Canarios do enqueue e do worker. <b>Nenhum deles toca API paga</b> — o unico ponto que
10
- /// gastaria dinheiro e o <see cref="FakeMediaProvider"/>, e a suite conta as chamadas dele.
11
- /// </summary>
12
- /// <remarks>
13
- /// As afirmacoes aqui sao as tres que o dinheiro cobra: um retry nao paga de novo, um
14
- /// estouro de teto recusa <b>antes</b> da chamada, e um bloqueio de moderacao vai para
15
- /// <c>RejectedByPolicy</c> em vez de virar retry.
16
- /// </remarks>
17
8
  public sealed class MediaGeneratorTests
18
9
  {
19
10
  private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
@@ -42,11 +33,6 @@ public sealed class MediaGeneratorTests
42
33
  jobs, store, provider);
43
34
  }
44
35
 
45
- // ---------------------------------------------------------------------------------
46
- // Enqueue: id de job, nunca bytes
47
- // ---------------------------------------------------------------------------------
48
-
49
- /// <summary>Um pedido valido vira job em <c>Pending</c>, e o retorno e um id.</summary>
50
36
  [Fact]
51
37
  public async Task EnqueueReturnsAJobIdAndTheJobStartsPending()
52
38
  {
@@ -58,11 +44,9 @@ public sealed class MediaGeneratorTests
58
44
  var job = await generator.GetAsync(enqueued.Value, Ct);
59
45
  Assert.Equal(MediaJobState.Pending, job.Value.State);
60
46
 
61
- // Nada foi gasto: o enqueue NAO chama provider.
62
47
  Assert.Equal(0, provider.Calls);
63
48
  }
64
49
 
65
- /// <summary>Pedido sem prompt e recusa nomeada, nao excecao.</summary>
66
50
  [Fact]
67
51
  public async Task AnInvalidRequestIsRefusedByName()
68
52
  {
@@ -75,7 +59,6 @@ public sealed class MediaGeneratorTests
75
59
  Assert.Equal(MediaErrorCode.InvalidRequest, result.Error!.Code);
76
60
  }
77
61
 
78
- /// <summary>Alias desconhecido: recusa, e nenhum job criado.</summary>
79
62
  [Fact]
80
63
  public async Task AnUnknownAliasIsRefused()
81
64
  {
@@ -88,14 +71,6 @@ public sealed class MediaGeneratorTests
88
71
  Assert.Equal(0, jobs.Saves);
89
72
  }
90
73
 
91
- // ---------------------------------------------------------------------------------
92
- // Idempotencia: o retry nao paga de novo
93
- // ---------------------------------------------------------------------------------
94
-
95
- /// <summary>
96
- /// O MESMO pedido, enfileirado duas vezes, devolve o MESMO job. E o que faz um retry de
97
- /// fila, de worker reiniciado ou de webhook duplicado custar zero.
98
- /// </summary>
99
74
  [Fact]
100
75
  public async Task TheSameRequestEnqueuedTwiceReusesTheSameJob()
101
76
  {
@@ -108,7 +83,6 @@ public sealed class MediaGeneratorTests
108
83
  Assert.Equal(1, jobs.Saves);
109
84
  }
110
85
 
111
- /// <summary>Pedido diferente, job diferente — a dedupe nao pode engolir trabalho novo.</summary>
112
86
  [Fact]
113
87
  public async Task ADifferentRequestGetsItsOwnJob()
114
88
  {
@@ -121,14 +95,6 @@ public sealed class MediaGeneratorTests
121
95
  Assert.NotEqual(first.Value, second.Value);
122
96
  }
123
97
 
124
- // ---------------------------------------------------------------------------------
125
- // Teto de custo: recusa ANTES da chamada
126
- // ---------------------------------------------------------------------------------
127
-
128
- /// <summary>
129
- /// O teto e confrontado com o custo estimado, e a recusa acontece no enqueue — antes de
130
- /// existir qualquer chamada. Depois dela, o dinheiro ja saiu.
131
- /// </summary>
132
98
  [Fact]
133
99
  public async Task ABudgetOverrunIsRefusedAtEnqueueBeforeAnyCall()
134
100
  {
@@ -145,7 +111,6 @@ public sealed class MediaGeneratorTests
145
111
  Assert.Equal([0.211m], budget.Seen);
146
112
  }
147
113
 
148
- /// <summary>O custo estimado que chega ao teto e o da tarifa, na quantidade pedida.</summary>
149
114
  [Fact]
150
115
  public async Task TheEstimateSentToTheBudgetIsTariffTimesCount()
151
116
  {
@@ -157,11 +122,6 @@ public sealed class MediaGeneratorTests
157
122
  Assert.Equal([0.024m], budget.Seen);
158
123
  }
159
124
 
160
- // ---------------------------------------------------------------------------------
161
- // Worker: estados, storage e a distincao que vale dinheiro
162
- // ---------------------------------------------------------------------------------
163
-
164
- /// <summary>Caminho feliz: os bytes vao para o storage ANTES de o job virar Generated.</summary>
165
125
  [Fact]
166
126
  public async Task TheWorkerStoresTheBytesAndThenMarksGenerated()
167
127
  {
@@ -178,7 +138,6 @@ public sealed class MediaGeneratorTests
178
138
  Assert.EndsWith(".png", ran.Value.RelativePath, StringComparison.Ordinal);
179
139
  }
180
140
 
181
- /// <summary>A proveniencia e gravada com o modelo, o prompt reescrito e o custo estimado.</summary>
182
141
  [Fact]
183
142
  public async Task TheWorkerRecordsProvenanceWithTheEstimatedCost()
184
143
  {
@@ -197,10 +156,6 @@ public sealed class MediaGeneratorTests
197
156
  Assert.True(provenance.C2paPreserved);
198
157
  }
199
158
 
200
- /// <summary>
201
- /// Bloqueio de moderacao vira <c>RejectedByPolicy</c>, terminal. Um erro tecnico vira
202
- /// <c>Failed</c>, retryable. Colapsar os dois e como um laco de retry vira fatura.
203
- /// </summary>
204
159
  [Theory]
205
160
  [InlineData(MediaErrorCode.PolicyBlocked, MediaJobState.RejectedByPolicy, false)]
206
161
  [InlineData(MediaErrorCode.ProviderFailure, MediaJobState.Failed, true)]
@@ -218,7 +173,6 @@ public sealed class MediaGeneratorTests
218
173
  Assert.Equal(0, store.Blobs.Writes);
219
174
  }
220
175
 
221
- /// <summary>Job inexistente e recusa nomeada, nunca uma excecao de referencia nula.</summary>
222
176
  [Fact]
223
177
  public async Task RunningAnUnknownJobIsRefusedByName()
224
178
  {
@@ -230,10 +184,6 @@ public sealed class MediaGeneratorTests
230
184
  Assert.Equal(MediaErrorCode.NotFound, ran.Error!.Code);
231
185
  }
232
186
 
233
- /// <summary>
234
- /// Um alias de VIDEO pedido como imagem nao vira job: as unidades de cobranca sao
235
- /// diferentes, e um pedido com a tarifa errada estima o custo errado em silencio.
236
- /// </summary>
237
187
  [Fact]
238
188
  public async Task AVideoAliasCannotServeAnImageRequest()
239
189
  {
@@ -246,9 +196,6 @@ public sealed class MediaGeneratorTests
246
196
  Assert.Equal(MediaErrorCode.InvalidRequest, result.Error!.Code);
247
197
  }
248
198
 
249
- /// <summary>
250
- /// Video sem duracao nao tem custo estimavel — e arbitrar segundos e arbitrar dinheiro.
251
- /// </summary>
252
199
  [Fact]
253
200
  public async Task AVideoRequestWithoutDurationCannotBePriced()
254
201
  {
@@ -4,15 +4,6 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests;
6
6
 
7
- /// <summary>
8
- /// Canarios de <see cref="MediaIdempotencyKey"/>: a chave que dedupe a CHAMADA PAGA.
9
- /// </summary>
10
- /// <remarks>
11
- /// Duas propriedades, e as duas custam dinheiro quando falham. <b>Estabilidade</b>: o mesmo
12
- /// pedido tem de dar a mesma chave, senao todo retry paga de novo. <b>Sensibilidade</b>:
13
- /// qualquer campo participante que mude tem de mudar a chave, senao um pedido reusa o
14
- /// artefato pago de OUTRO — pior que pagar duas vezes.
15
- /// </remarks>
16
7
  public sealed class MediaIdempotencyKeyTests
17
8
  {
18
9
  private static MediaRequest Request(
@@ -29,32 +20,18 @@ public sealed class MediaIdempotencyKeyTests
29
20
  private static string KeyOf(MediaRequest request)
30
21
  => MediaIdempotencyKey.From(request, "openai", "gpt-image-2").Value;
31
22
 
32
- // ---------------------------------------------------------------------------------
33
- // Estabilidade
34
- // ---------------------------------------------------------------------------------
35
-
36
- /// <summary>Duas construcoes do mesmo pedido dao a mesma chave.</summary>
37
23
  [Fact]
38
24
  public void TheSameRequestAlwaysGivesTheSameKey()
39
25
  => Assert.Equal(KeyOf(Request()), KeyOf(Request()));
40
26
 
41
- /// <summary>
42
- /// Espaco em branco a mais nao muda o pedido, entao nao muda a chave. E o que impede um
43
- /// prompt re-serializado com quebra de linha diferente de virar outra fatura.
44
- /// </summary>
45
27
  [Fact]
46
28
  public void WhitespaceIsNormalisedAway()
47
29
  => Assert.Equal(KeyOf(Request("um sofa verde")), KeyOf(Request(" um sofa\n verde ")));
48
30
 
49
- /// <summary>
50
- /// A caixa NAO e normalizada, e e deliberado: num prompt ela muda o resultado (nome de
51
- /// marca, texto literal que vai aparecer na imagem).
52
- /// </summary>
53
31
  [Fact]
54
32
  public void CaseIsPreservedBecauseItChangesThePicture()
55
33
  => Assert.NotEqual(KeyOf(Request("um sofa VERDE")), KeyOf(Request("um sofa verde")));
56
34
 
57
- /// <summary>A chave e um sha256 em hex minusculo: 64 digitos.</summary>
58
35
  [Fact]
59
36
  public void TheKeyIsASha256InLowercaseHex()
60
37
  {
@@ -64,58 +41,43 @@ public sealed class MediaIdempotencyKeyTests
64
41
  Assert.Matches("^[0-9a-f]{64}$", key);
65
42
  }
66
43
 
67
- // ---------------------------------------------------------------------------------
68
- // Sensibilidade — uma rodada POR CAMPO participante
69
- // ---------------------------------------------------------------------------------
70
-
71
- /// <summary>Prompt diferente, chave diferente.</summary>
72
44
  [Fact]
73
45
  public void ADifferentPromptChangesTheKey()
74
46
  => Assert.NotEqual(KeyOf(Request("um sofa azul")), KeyOf(Request()));
75
47
 
76
- /// <summary>Modelo diferente, chave diferente — o alias pode ser repontado.</summary>
77
48
  [Fact]
78
49
  public void ADifferentModelChangesTheKey()
79
50
  => Assert.NotEqual(
80
51
  MediaIdempotencyKey.From(Request(), "openai", "gpt-image-2").Value,
81
52
  MediaIdempotencyKey.From(Request(), "openai", "outro-modelo").Value);
82
53
 
83
- /// <summary>Provider diferente, chave diferente.</summary>
84
54
  [Fact]
85
55
  public void ADifferentProviderChangesTheKey()
86
56
  => Assert.NotEqual(
87
57
  MediaIdempotencyKey.From(Request(), "openai", "m").Value,
88
58
  MediaIdempotencyKey.From(Request(), "google", "m").Value);
89
59
 
90
- /// <summary>
91
- /// <b>Trocar a foto de referencia E outro pedido</b>, mesmo com o prompt identico. Por
92
- /// isso o sha256 de CADA referencia participa, e nao a contagem delas.
93
- /// </summary>
94
60
  [Fact]
95
61
  public void ADifferentReferenceChangesTheKey()
96
62
  => Assert.NotEqual(
97
63
  KeyOf(Request(references: ["foto-a"])),
98
64
  KeyOf(Request(references: ["foto-b"])));
99
65
 
100
- /// <summary>Mesma quantidade de referencias, conteudo trocado de ordem: outro pedido.</summary>
101
66
  [Fact]
102
67
  public void TheOrderOfReferencesIsPartOfTheRequest()
103
68
  => Assert.NotEqual(
104
69
  KeyOf(Request(references: ["a", "b"])),
105
70
  KeyOf(Request(references: ["b", "a"])));
106
71
 
107
- /// <summary>Sem referencia e com referencia sao pedidos diferentes.</summary>
108
72
  [Fact]
109
73
  public void AddingAReferenceChangesTheKey()
110
74
  => Assert.NotEqual(KeyOf(Request()), KeyOf(Request(references: ["foto-a"])));
111
75
 
112
- /// <summary>Cada opcao participante, uma rodada.</summary>
113
76
  [Theory]
114
77
  [MemberData(nameof(OptionVariations))]
115
78
  public void EveryParticipatingOptionChangesTheKey(MediaOptions variation)
116
79
  => Assert.NotEqual(KeyOf(Request()), KeyOf(Request(options: variation)));
117
80
 
118
- /// <summary>As variacoes de opcao exercitadas, uma por campo canonico.</summary>
119
81
  public static TheoryData<MediaOptions> OptionVariations() =>
120
82
  [
121
83
  new MediaOptions { Quality = MediaQuality.High },
@@ -126,18 +88,12 @@ public sealed class MediaIdempotencyKeyTests
126
88
  new MediaOptions { AspectRatio = "9:16" },
127
89
  ];
128
90
 
129
- /// <summary>
130
- /// O separador e um caractere de CONTROLE, e nao <c>|</c>. Um prompt que contivesse o
131
- /// separador visivel emendaria campos e faria dois pedidos diferentes colidirem — que
132
- /// e o defeito que faz um pedido reusar o artefato pago do outro.
133
- /// </summary>
134
91
  [Fact]
135
92
  public void APromptFullOfPipesDoesNotCollideWithAnotherRequest()
136
93
  => Assert.NotEqual(
137
94
  KeyOf(Request("gpt-image-2|Image|um sofa")),
138
95
  KeyOf(Request("um sofa")));
139
96
 
140
- /// <summary>Normalizacao de prompt e pura e conferivel sozinha.</summary>
141
97
  [Theory]
142
98
  [InlineData(" a b ", "a b")]
143
99
  [InlineData("a\n\nb", "a b")]
@@ -3,23 +3,10 @@ using Xunit;
3
3
 
4
4
  namespace Morph.AiKit.Tests;
5
5
 
6
- /// <summary>
7
- /// Canarios da maquina de estados de <see cref="MediaJob"/>.
8
- /// </summary>
9
- /// <remarks>
10
- /// Tres afirmacoes carregam o standard <c>ai-agents/media-pipeline.md</c> inteiro, e cada
11
- /// uma e o inverso literal de um defeito caro:
12
- /// <list type="number">
13
- /// <item><c>Publish</c> falha fora de <c>Approved</c> — publicar sem revisao humana.</item>
14
- /// <item><c>Approved</c> so existe por <c>Approve(revisor)</c> — "aprovado" sem quem.</item>
15
- /// <item><c>RejectedByPolicy</c> nao e retryable — retry que vira fatura.</item>
16
- /// </list>
17
- /// </remarks>
18
6
  public sealed class MediaJobStateTests
19
7
  {
20
8
  private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
21
9
 
22
- /// <summary>Uma marcacao valida, para os casos que nao estao medindo a marcacao.</summary>
23
10
  private static MediaDisclosure Label => MediaDisclosure.Create("Imagem gerada por IA", "legenda", T0);
24
11
 
25
12
  private static MediaJob Pending() => MediaJob.Create(
@@ -37,14 +24,6 @@ public sealed class MediaJobStateTests
37
24
  .Value;
38
25
  }
39
26
 
40
- // ---------------------------------------------------------------------------------
41
- // 1. Publish so a partir de Approved
42
- // ---------------------------------------------------------------------------------
43
-
44
- /// <summary>
45
- /// O atalho que alguem sempre quer: publicar direto de <c>Generated</c>. Ele nao
46
- /// existe, e a mensagem diz por que.
47
- /// </summary>
48
27
  [Fact]
49
28
  public void PublishFailsFromGeneratedBecauseNobodyReviewedIt()
50
29
  {
@@ -55,7 +34,6 @@ public sealed class MediaJobStateTests
55
34
  Assert.Contains("Approved", result.Error.Message, StringComparison.Ordinal);
56
35
  }
57
36
 
58
- /// <summary>Publish falha a partir de TODO estado que nao seja Approved — nao so de Generated.</summary>
59
37
  [Theory]
60
38
  [InlineData(MediaJobState.Pending)]
61
39
  [InlineData(MediaJobState.Generating)]
@@ -71,7 +49,6 @@ public sealed class MediaJobStateTests
71
49
  Assert.False(job.Publish(Label, T0).IsSuccess);
72
50
  }
73
51
 
74
- /// <summary>E, com a revisao humana no lugar, publica.</summary>
75
52
  [Fact]
76
53
  public void PublishSucceedsFromApproved()
77
54
  {
@@ -84,11 +61,6 @@ public sealed class MediaJobStateTests
84
61
  Assert.Equal(T0, published.Value.PublishedAt);
85
62
  }
86
63
 
87
- // ---------------------------------------------------------------------------------
88
- // 2. Approved so por Approve(revisor)
89
- // ---------------------------------------------------------------------------------
90
-
91
- /// <summary>A aprovacao carrega quem e quando — sem isso e um campo booleano.</summary>
92
64
  [Fact]
93
65
  public void ApproveStampsTheReviewerAndTheTime()
94
66
  {
@@ -100,15 +72,10 @@ public sealed class MediaJobStateTests
100
72
  Assert.Equal("cor confere", approved.Review.Note);
101
73
  }
102
74
 
103
- /// <summary>Revisor em branco nao carimba nada, e a recusa e na construcao da revisao.</summary>
104
75
  [Fact]
105
76
  public void AnAnonymousReviewIsRefusedAtConstruction()
106
77
  => Assert.Throws<ArgumentException>(() => MediaReview.ApprovedBy(" ", T0));
107
78
 
108
- /// <summary>
109
- /// Aprovar com um veredito de REJEICAO seria registrar o oposto do que o humano
110
- /// decidiu. O tipo recusa.
111
- /// </summary>
112
79
  [Fact]
113
80
  public void ApproveRefusesAReviewThatSaysRejected()
114
81
  {
@@ -118,19 +85,10 @@ public sealed class MediaJobStateTests
118
85
  Assert.Equal(MediaErrorCode.InvalidTransition, result.Error!.Code);
119
86
  }
120
87
 
121
- /// <summary>Aprovar antes de existirem bytes nao e transicao.</summary>
122
88
  [Fact]
123
89
  public void ApproveFailsBeforeTheBytesExist()
124
90
  => Assert.False(Pending().Approve(MediaReview.ApprovedBy("lucas", T0)).IsSuccess);
125
91
 
126
- // ---------------------------------------------------------------------------------
127
- // 3. RejectedByPolicy nao e retryable; Failed e
128
- // ---------------------------------------------------------------------------------
129
-
130
- /// <summary>
131
- /// A distincao que vale dinheiro. Bloqueio de moderacao e resposta definitiva; erro
132
- /// tecnico e tentativa perdida.
133
- /// </summary>
134
92
  [Fact]
135
93
  public void PolicyBlockIsTerminalAndTechnicalFailureIsNot()
136
94
  {
@@ -146,7 +104,6 @@ public sealed class MediaJobStateTests
146
104
  Assert.True(failed.StartGenerating().IsSuccess);
147
105
  }
148
106
 
149
- /// <summary>Um job em <c>Generated</c> carrega hash, caminho e proveniencia — os tres juntos.</summary>
150
107
  [Fact]
151
108
  public void GeneratedCarriesHashPathAndProvenanceTogether()
152
109
  {
@@ -158,7 +115,6 @@ public sealed class MediaJobStateTests
158
115
  Assert.Equal("gpt-image-2", job.Provenance!.ModelId);
159
116
  }
160
117
 
161
- /// <summary>Uma transicao recusada NAO muda o job: o registro e imutavel.</summary>
162
118
  [Fact]
163
119
  public void ARefusedTransitionLeavesTheJobUntouched()
164
120
  {
@@ -3,15 +3,6 @@ using Xunit;
3
3
 
4
4
  namespace Morph.AiKit.Tests;
5
5
 
6
- /// <summary>
7
- /// Canarios de <see cref="MediaReview"/>.
8
- /// </summary>
9
- /// <remarks>
10
- /// A afirmacao que este arquivo protege nao e "aprovar funciona" — e que
11
- /// <see cref="MediaReview"/> <b>delega</b> ao <see cref="MediaJob"/> em vez de reimplementar
12
- /// a regra. Uma segunda copia de "so Approved publica" e exatamente como as duas divergem, e
13
- /// e o padrao de falha que esta epic catalogou sete vezes: o teste mede uma COPIA do objeto.
14
- /// </remarks>
15
6
  public sealed class MediaReviewTests
16
7
  {
17
8
  private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
@@ -31,7 +22,6 @@ public sealed class MediaReviewTests
31
22
  new MediaProvenance("openai", "gpt-image-2", "p", null, [], "-", null, 0m, T0, true)).Value;
32
23
  }
33
24
 
34
- /// <summary>O atalho aprova e carimba.</summary>
35
25
  [Fact]
36
26
  public void ApproveDelegatesAndStamps()
37
27
  {
@@ -42,7 +32,6 @@ public sealed class MediaReviewTests
42
32
  Assert.Equal("lucas", result.Value.Review!.Reviewer);
43
33
  }
44
34
 
45
- /// <summary>Rejeitar e terminal e tambem carimba.</summary>
46
35
  [Fact]
47
36
  public void RejectDelegatesAndStamps()
48
37
  {
@@ -53,12 +42,6 @@ public sealed class MediaReviewTests
53
42
  Assert.Equal(MediaReviewDecision.Rejected, result.Value.Review!.Decision);
54
43
  }
55
44
 
56
- /// <summary>
57
- /// O atalho de publicar NAO tem uma regra propria: ele recusa exatamente onde
58
- /// <see cref="MediaJob.Publish"/> recusa. Confere-se comparando as DUAS chamadas sobre o
59
- /// MESMO job — se <c>MediaReview</c> tivesse copiado a regra, aqui e onde a copia
60
- /// divergiria.
61
- /// </summary>
62
45
  [Fact]
63
46
  public void PublishThroughTheReviewFacadeAgreesWithTheJobItself()
64
47
  {
@@ -72,12 +55,10 @@ public sealed class MediaReviewTests
72
55
  Assert.True(MediaReview.Publish(approved, Label, T0).IsSuccess);
73
56
  }
74
57
 
75
- /// <summary>Job nulo e falha de composicao, nao Result: quem chama errou o codigo.</summary>
76
58
  [Fact]
77
59
  public void ANullJobIsAProgrammingError()
78
60
  => Assert.Throws<ArgumentNullException>(() => MediaReview.Approve(null!, "lucas", T0));
79
61
 
80
- /// <summary>Um juiz LLM cabe na nota, nunca no campo do revisor de uma foto de catalogo.</summary>
81
62
  [Fact]
82
63
  public void TheNoteCarriesThePreFilterVerdict()
83
64
  {
@@ -5,22 +5,12 @@ using Xunit;
5
5
 
6
6
  namespace Morph.AiKit.Tests;
7
7
 
8
- /// <summary>
9
- /// Canarios do <see cref="MediaStore"/>: endereco por conteudo, e gravacao unica.
10
- /// </summary>
11
- /// <remarks>
12
- /// A armadilha deste arquivo, e a razao de <see cref="InMemoryMediaBlobs.Writes"/> existir:
13
- /// um teste que so conferisse "o hash e o mesmo" <b>nao distingue dedupe de regravacao</b>,
14
- /// porque os dois produzem o mesmo hash. A afirmacao "grava uma vez" so e verificavel
15
- /// contando gravacoes no blob.
16
- /// </remarks>
17
8
  public sealed class MediaStoreTests
18
9
  {
19
10
  private static CancellationToken Ct => TestContext.Current.CancellationToken;
20
11
 
21
12
  private static ReadOnlyMemory<byte> Bytes(string s) => Encoding.UTF8.GetBytes(s);
22
13
 
23
- /// <summary>Mesmo conteudo, mesmo hash, mesmo caminho — e UMA gravacao.</summary>
24
14
  [Fact]
25
15
  public async Task TheSameContentIsStoredOnce()
26
16
  {
@@ -37,7 +27,6 @@ public sealed class MediaStoreTests
37
27
  Assert.Equal(1, blobs.Writes);
38
28
  }
39
29
 
40
- /// <summary>Conteudo diferente, endereco diferente, duas gravacoes.</summary>
41
30
  [Fact]
42
31
  public async Task DifferentContentGetsADifferentAddress()
43
32
  {
@@ -52,18 +41,10 @@ public sealed class MediaStoreTests
52
41
  Assert.Equal(2, blobs.Writes);
53
42
  }
54
43
 
55
- /// <summary>
56
- /// O hash e o do CONTEUDO, e nao do conteudo mais o MIME: o mesmo PNG servido como
57
- /// <c>image/png</c> e como <c>application/octet-stream</c> e o mesmo arquivo.
58
- /// </summary>
59
44
  [Fact]
60
45
  public void TheHashIsOfTheContentAlone()
61
46
  => Assert.Equal(MediaHash.Of(Bytes("x").Span), MediaHash.Of(Bytes("x").Span));
62
47
 
63
- /// <summary>
64
- /// O caminho tem dois niveis de prefixo. Um diretorio unico com centenas de milhares de
65
- /// arquivos degrada listagem em praticamente todo sistema de arquivos.
66
- /// </summary>
67
48
  [Fact]
68
49
  public void ThePathIsShardedByTheFirstFourHexDigits()
69
50
  {
@@ -73,10 +54,6 @@ public sealed class MediaStoreTests
73
54
  Assert.Equal($"{hash.Value[..2]}/{hash.Value[2..4]}/{hash.Value}.png", path);
74
55
  }
75
56
 
76
- /// <summary>
77
- /// MIME desconhecido vira <c>.bin</c> e NAO recusa: perder a extensao e cosmetico,
78
- /// perder os bytes pelos quais se pagou nao e.
79
- /// </summary>
80
57
  [Theory]
81
58
  [InlineData("image/png", ".png")]
82
59
  [InlineData("image/jpeg", ".jpg")]
@@ -86,7 +63,6 @@ public sealed class MediaStoreTests
86
63
  public void UnknownMediaTypesStillGetAPath(string mediaType, string expected)
87
64
  => Assert.Equal(expected, MediaStore.ExtensionFor(mediaType));
88
65
 
89
- /// <summary>MIME em branco e defeito de chamada, e grita no lugar em que aconteceu.</summary>
90
66
  [Fact]
91
67
  public async Task ABlankMediaTypeIsRefused()
92
68
  {