| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- using Admin.NET.Plugin.AiDOP;
- using Admin.NET.Plugin.AiDOP.Supply;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.S8.Supply;
- /// <summary>
- /// S8 Stage-3 采购完成态事实(<c>mdp_std_purchase_order_completion</c>,1.0.544)的生产契约。
- ///
- /// <para>这条链让 S8 在<b>不覆盖</b> <c>mdp_std_purchase_order.status</c> 的前提下拿到 WMS 侧
- /// 「这条采购行是否收货关闭」。一旦被"顺手优化"破坏,S8 会在毫无征兆的情况下
- /// 把未知当成未完成、把已回退的行当成仍然完成,或者把事实写到别的租户名下。</para>
- ///
- /// <para>本文件守住六条最易被破坏的边界:
- /// ① 三态映射 —— NULL / 未知值绝不能滑成 NOT_COMPLETED;
- /// ② 可逆 —— UPSERT 必须无条件覆盖结论,不许有"一旦完成永远完成"的守卫;
- /// ③ 租户 —— 唯一才用,歧义与未解析一律跳过,绝不"取第一个",绝不用 Domain 推租户;
- /// ④ 淘汰 —— 只在源侧读取成功后执行,且必须 tenant-safe;
- /// ⑤ 源绑定 —— 配置化 + 非开发环境 fail-closed,且必须挡住"回落主库";
- /// ⑥ 边界 —— 不读 IQC、不用数量推完成、不写任何源表。</para>
- /// </summary>
- public class PurchaseOrderCompletionContractTests
- {
- private static string Source(params string[] parts)
- {
- var dir = new DirectoryInfo(AppContext.BaseDirectory);
- while (dir != null && !Directory.Exists(Path.Combine(dir.FullName, "Admin.NET.Plugin.AiDOP")))
- dir = dir.Parent;
- Assert.NotNull(dir);
- var full = Path.Combine(new[] { dir!.FullName, "Admin.NET.Plugin.AiDOP" }.Concat(parts).ToArray());
- Assert.True(File.Exists(full), $"源码文件不存在,路径需同步更新:{full}");
- return File.ReadAllText(full);
- }
- private static string Service() => Source("Supply", "PurchaseOrderCompletionMdpSyncService.cs");
- /// <summary>
- /// 去掉 XML 文档注释后的代码体。禁止类断言必须扫这个 —— 文档注释需要点名
- /// 「不读 IQC」「回到 NOT_OBSERVED」这些概念来说明边界,那是说明而不是违规。
- /// </summary>
- private static string ServiceBody()
- => string.Join('\n', Service().Split('\n').Where(l => !l.TrimStart().StartsWith("///", StringComparison.Ordinal)));
- private static string Migration(string name)
- {
- var dir = new DirectoryInfo(AppContext.BaseDirectory);
- while (dir != null && !Directory.Exists(Path.Combine(dir.FullName, "Admin.NET.Web.Entry")))
- dir = dir.Parent;
- Assert.NotNull(dir);
- var full = Path.Combine(dir!.FullName, "Admin.NET.Web.Entry", "UpdateScripts", name);
- Assert.True(File.Exists(full), $"迁移脚本不存在:{full}");
- return File.ReadAllText(full);
- }
- private static int At(string code, string anchor)
- {
- var i = code.IndexOf(anchor, StringComparison.Ordinal);
- Assert.True(i >= 0, $"未找到锚点:{anchor}");
- return i;
- }
- private static string Between(string code, string startAnchor, string endAnchor)
- {
- var s = At(code, startAnchor);
- var e = At(code, endAnchor);
- Assert.True(e > s, $"锚点顺序不符:{startAnchor} 应在 {endAnchor} 之前");
- return code[s..e];
- }
- // ── ① 三态映射 ───────────────────────────────────────────────────────────────
- [Theory]
- [InlineData("C")]
- [InlineData("c")]
- [InlineData(" C ")]
- public void 状态C一律映射为COMPLETED(string raw)
- => Assert.Equal("COMPLETED", PurchaseOrderCompletionMdpSyncService.MapCompletionStatus(raw));
- [Theory]
- [InlineData("")]
- [InlineData(" ")]
- public void 空串映射为NOT_COMPLETED(string raw)
- => Assert.Equal("NOT_COMPLETED", PurchaseOrderCompletionMdpSyncService.MapCompletionStatus(raw));
- [Fact]
- public void NULL绝不能滑成NOT_COMPLETED()
- {
- // 「源侧没给值」不等于「已确认尚未完成」,下游会把它当确定结论用
- Assert.Equal("UNKNOWN", PurchaseOrderCompletionMdpSyncService.MapCompletionStatus(null));
- }
- [Theory]
- [InlineData("R")]
- [InlineData("O")]
- [InlineData("S")]
- [InlineData("X")]
- public void 未知非空值一律UNKNOWN(string raw)
- => Assert.Equal("UNKNOWN", PurchaseOrderCompletionMdpSyncService.MapCompletionStatus(raw));
- [Fact]
- public void 映射取值域封闭()
- {
- var allowed = new[] { "COMPLETED", "NOT_COMPLETED", "UNKNOWN" };
- foreach (var raw in new string?[] { null, "", " ", "C", "c", "R", "O", "Z", "完成" })
- Assert.Contains(PurchaseOrderCompletionMdpSyncService.MapCompletionStatus(raw), allowed);
- }
- [Fact]
- public void raw_status归一保留NULL()
- {
- Assert.Null(PurchaseOrderCompletionMdpSyncService.NormalizeRawStatus(null));
- Assert.Equal("", PurchaseOrderCompletionMdpSyncService.NormalizeRawStatus(" "));
- Assert.Equal("C", PurchaseOrderCompletionMdpSyncService.NormalizeRawStatus(" C "));
- }
- [Fact]
- public void 禁止用IFNULL把NULL和空串合并()
- {
- var code = Service();
- var mapper = Between(code, "public static string MapCompletionStatus", "public static string? NormalizeRawStatus");
- // 出现 ?? "" 或 IFNULL 语义即意味着 NULL 被当成空串处理
- Assert.DoesNotContain("?? \"\"", mapper, StringComparison.Ordinal);
- Assert.DoesNotContain("?? string.Empty", mapper, StringComparison.Ordinal);
- Assert.Contains("if (rawStatus is null) return StatusUnknown;", mapper, StringComparison.Ordinal);
- }
- // ── ② 可逆:COMPLETED → NOT_COMPLETED 必须能回退 ──────────────────────────────
- [Fact]
- public void 回退序列_空到C再回空()
- {
- // 同一 Grain 连续三次观测,结论必须跟着源侧走
- Assert.Equal("NOT_COMPLETED", PurchaseOrderCompletionMdpSyncService.MapCompletionStatus(""));
- Assert.Equal("COMPLETED", PurchaseOrderCompletionMdpSyncService.MapCompletionStatus("C"));
- Assert.Equal("NOT_COMPLETED", PurchaseOrderCompletionMdpSyncService.MapCompletionStatus(""));
- }
- [Fact]
- public void UPSERT必须无条件覆盖结论_不得有一旦完成永远完成的守卫()
- {
- var block = Between(Service(), "ON DUPLICATE KEY UPDATE", "await _db.Ado.ExecuteCommandAsync(sb.ToString()");
- Assert.Contains("raw_status=VALUES(raw_status)", block, StringComparison.Ordinal);
- Assert.Contains("completion_status=VALUES(completion_status)", block, StringComparison.Ordinal);
- // 任何 IF(...) / CASE / COALESCE 形式的保留旧值写法都会让回退静默失效
- foreach (var forbidden in new[]
- {
- "completion_status = IF(", "completion_status=IF(",
- "raw_status = IF(", "raw_status=IF(",
- "CASE WHEN completion_status", "COALESCE(completion_status",
- })
- Assert.DoesNotContain(forbidden, block, StringComparison.Ordinal);
- }
- [Fact]
- public void 同一Grain只能一行_唯一键就位()
- {
- var sql = Migration("1.0.544.sql");
- Assert.Contains(
- "UNIQUE KEY `uk_mdp_std_po_completion` (`tenant_id`, `po_no`, `po_line`)",
- sql, StringComparison.Ordinal);
- }
- [Fact]
- public void provenance列不得混进业务唯一键()
- {
- var sql = Migration("1.0.544.sql");
- foreach (var forbidden in new[]
- {
- "uk_mdp_std_po_completion` (`tenant_id`, `domain`",
- "uk_mdp_std_po_completion` (`tenant_id`, `source_row_id`",
- })
- Assert.DoesNotContain(forbidden, sql, StringComparison.Ordinal);
- }
- // ── ③ 租户归属:唯一才用 ─────────────────────────────────────────────────────
- private static List<PoTenantCandidate> Candidates(params (string po, string dom, long tid)[] rows)
- => rows.Select(r => new PoTenantCandidate { PoNo = r.po, Domain = r.dom, TenantId = r.tid }).ToList();
- [Fact]
- public void 租户唯一时使用()
- {
- var c = Candidates(("PO1", "", 797L), ("PO2", "", 838L));
- Assert.Equal(797L, PurchaseOrderCompletionMdpSyncService.ResolveTenantId(c, "PO1", "8010"));
- }
- [Fact]
- public void 租户无候选时返回0_不得臆造()
- {
- var c = Candidates(("PO1", "", 797L));
- Assert.Equal(0L, PurchaseOrderCompletionMdpSyncService.ResolveTenantId(c, "PO-NOT-EXIST", "8010"));
- }
- [Fact]
- public void 租户歧义时返回0_绝不取第一个()
- {
- // 同一采购单号在两个租户下都存在 —— 必须整体跳过,不能任选
- var c = Candidates(("PO1", "", 797L), ("PO1", "", 838L));
- Assert.Equal(0L, PurchaseOrderCompletionMdpSyncService.ResolveTenantId(c, "PO1", "8010"));
- }
- [Fact]
- public void 空Domain视为通配_自建单可命中()
- {
- var c = Candidates(("PO1", "", 797L));
- Assert.Equal(797L, PurchaseOrderCompletionMdpSyncService.ResolveTenantId(c, "PO1", "8010"));
- }
- [Fact]
- public void Domain不一致的候选被排除()
- {
- var c = Candidates(("PO1", "9999", 797L));
- Assert.Equal(0L, PurchaseOrderCompletionMdpSyncService.ResolveTenantId(c, "PO1", "8010"));
- }
- [Fact]
- public void 相同Domain不会把不同租户混成一个()
- {
- // Domain 8010 横跨多个租户,绝不能因为 Domain 相同就认为是同一租户
- var c = Candidates(("PO1", "8010", 797L), ("PO1", "8010", 838L));
- Assert.Equal(0L, PurchaseOrderCompletionMdpSyncService.ResolveTenantId(c, "PO1", "8010"));
- }
- [Fact]
- public void 同一租户多行不算歧义()
- {
- var c = Candidates(("PO1", "", 797L), ("PO1", "8010", 797L));
- Assert.Equal(797L, PurchaseOrderCompletionMdpSyncService.ResolveTenantId(c, "PO1", "8010"));
- }
- [Fact]
- public void 禁止用Domain映射表反推租户()
- {
- var code = ServiceBody();
- // ado_source_domain_tenant_map 全表只有一行,用它会把 838 的数据错记到 797 名下
- Assert.DoesNotContain("ado_source_domain_tenant_map", code, StringComparison.Ordinal);
- Assert.DoesNotContain("ResolveDomainAsync", code, StringComparison.Ordinal);
- }
- [Fact]
- public void 租户解析不到必须跳过_不得落无主Fact()
- {
- var run = Between(Service(), "var resolved = ResolveTenant(", "// 单租户刷新");
- Assert.Contains("if (resolved <= 0)", run, StringComparison.Ordinal);
- Assert.Contains("continue;", run, StringComparison.Ordinal);
- }
- // ── ④ 本地采购行校验 ─────────────────────────────────────────────────────────
- [Fact]
- public void 多命中必须硬失败_不得取第一条()
- {
- var run = Between(Service(), "var matches = poLineIndex.TryGetValue", "result.MatchedRows++");
- Assert.Contains("if (matches > 1)", run, StringComparison.Ordinal);
- Assert.Contains("throw new InvalidOperationException", run, StringComparison.Ordinal);
- Assert.Contains("if (matches == 0)", run, StringComparison.Ordinal);
- foreach (var forbidden in new[] { "LIMIT 1", ".First()", ".Min()", ".Max()" })
- Assert.DoesNotContain(forbidden, run, StringComparison.Ordinal);
- }
- // ── ⑤ 淘汰:只在成功后、且 tenant-safe ───────────────────────────────────────
- [Fact]
- public void 淘汰必须在源读取与写入成功之后()
- {
- var code = Service();
- var upsert = At(code, "result.WrittenRows = await UpsertAsync");
- var retire = At(code, "result.StaleRemoved = await RetireStaleAsync");
- var read = At(code, "var sourceRows = await ReadSourceRowsAsync");
- Assert.True(read < upsert, "必须先读源再写入");
- Assert.True(upsert < retire, "必须先写入再淘汰,读取失败时异常会跳过淘汰");
- }
- [Fact]
- public void 异常分支绝不淘汰()
- {
- var catchBlock = Between(Service(), "catch (Exception ex)", "// ── 源绑定与 fail-closed");
- Assert.DoesNotContain("RetireStaleAsync", catchBlock, StringComparison.Ordinal);
- Assert.DoesNotContain("DELETE", catchBlock, StringComparison.Ordinal);
- }
- [Fact]
- public void 单租户刷新只淘汰该租户()
- {
- var retire = Between(Service(), "private async Task<int> RetireStaleAsync", "// ── run log");
- Assert.Contains("WHERE tenant_id=@TenantId AND IFNULL(sync_batch_id,'') <> @BatchId", retire, StringComparison.Ordinal);
- // 全量路径也必须显式排除无主行
- Assert.Contains("WHERE tenant_id > 0 AND IFNULL(sync_batch_id,'') <> @BatchId", retire, StringComparison.Ordinal);
- // 绝不允许无条件 DELETE
- Assert.DoesNotContain("DELETE FROM mdp_std_purchase_order_completion\"", retire, StringComparison.Ordinal);
- }
- [Fact]
- public void 单租户刷新时其它租户的源行不参与本轮()
- {
- // 否则它们会因为没盖上本批次号而被误删
- var run = Between(Service(), "// 单租户刷新", "var matches = poLineIndex.TryGetValue");
- Assert.Contains("if (tenantId > 0 && resolved != tenantId)", run, StringComparison.Ordinal);
- Assert.Contains("continue;", run, StringComparison.Ordinal);
- }
- // ── ⑥ 源绑定与 fail-closed ───────────────────────────────────────────────────
- [Fact]
- public void 源编码必须来自配置_业务代码内不得出现具体源字面量()
- {
- var code = Service();
- Assert.DoesNotContain("DOPDEMORQ", code, StringComparison.OrdinalIgnoreCase);
- Assert.Contains("_opt.SourceCode", code, StringComparison.Ordinal);
- Assert.Contains("_scopeFactory.GetScopeAsync(binding.SourceCode", code, StringComparison.Ordinal);
- }
- [Fact]
- public void 配置类提供默认值与DEV白名单()
- {
- var opt = Source("Option", "AidopPoCompletionOptions.cs");
- Assert.Contains("[OptionsSettings(\"AiDOP:PoCompletion\")]", opt, StringComparison.Ordinal);
- Assert.Contains("IConfigurableOptions", opt, StringComparison.Ordinal);
- Assert.Contains("public string SourceCode", opt, StringComparison.Ordinal);
- Assert.Contains("public string[] DevOnlySourceCodes", opt, StringComparison.Ordinal);
- }
- [Fact]
- public void 非开发环境命中DEV源必须fail_closed()
- {
- var bind = Between(Service(), "private async Task<SourceBinding> ResolveSourceBindingAsync", "// ── 源侧读取");
- Assert.Contains("_env.IsDevelopment()", bind, StringComparison.Ordinal);
- Assert.Contains("DevOnlySourceCodes", bind, StringComparison.Ordinal);
- Assert.Contains("throw new InvalidOperationException", bind, StringComparison.Ordinal);
- }
- [Fact]
- public void 源未配置或被禁用必须失败()
- {
- var bind = Between(Service(), "private async Task<SourceBinding> ResolveSourceBindingAsync", "// ── 源侧读取");
- Assert.Contains("code.Length == 0", bind, StringComparison.Ordinal);
- Assert.Contains("status=1", bind, StringComparison.Ordinal);
- Assert.Contains("mdp_source 未找到启用源", bind, StringComparison.Ordinal);
- }
- [Fact]
- public void 必须挡住回落主库()
- {
- var code = Service();
- // 作用域工厂对本库样板源与未配账号的源会直接返回主库连接,
- // 那会让本服务把 Source A 的本地表当成 WMS 执行态读进来
- Assert.Contains("ReferenceEquals(remote, _db)", code, StringComparison.Ordinal);
- var bind = Between(code, "private async Task<SourceBinding> ResolveSourceBindingAsync", "// ── 源侧读取");
- Assert.Contains("IsNullOrWhiteSpace(src.DbUser)", bind, StringComparison.Ordinal);
- }
- [Fact]
- public void 回执与日志不得暴露连接信息()
- {
- var code = Service();
- foreach (var forbidden in new[] { "ConnectionString", "DbPassword", "db_password", "Password=" })
- Assert.DoesNotContain(forbidden, code, StringComparison.OrdinalIgnoreCase);
- }
- // ── ⑦ 边界:唯一 Authority 是源侧 Status ─────────────────────────────────────
- [Fact]
- public void 禁止用数量重新推导完成()
- {
- var code = ServiceBody();
- foreach (var forbidden in new[]
- {
- "received_qty", "order_qty", "QtyOrded", "QtyReceived", "RctQty",
- "QtyReturned", "ReceiptQty", "mdp_std_purchase_receipt",
- })
- Assert.DoesNotContain(forbidden, code, StringComparison.Ordinal);
- }
- [Fact]
- public void 禁止读IQC或算ObjectCompleted()
- {
- var code = ServiceBody();
- foreach (var forbidden in new[]
- {
- "mdp_std_iqc_inspection", "quality_result", "flow_status",
- "ObjectCompleted", "QualityAccepted", "StageComplete",
- })
- Assert.DoesNotContain(forbidden, code, StringComparison.Ordinal);
- }
- [Fact]
- public void 只写completion事实表与跑批日志()
- {
- var code = Service();
- var targets = code.Split("INSERT INTO ").Skip(1)
- .Select(x => x.Split('\n')[0].Trim().Split(' ')[0].Trim())
- .Distinct().OrderBy(x => x, StringComparer.Ordinal).ToArray();
- Assert.Equal(new[] { "mdp_std_purchase_order_completion", "mdp_transform_run_log" }, targets);
- }
- [Fact]
- public void 绝不更新SourceA的基础事实()
- {
- var code = ServiceBody();
- foreach (var forbidden in new[]
- {
- "UPDATE mdp_std_purchase_order\n", "UPDATE mdp_std_purchase_order ",
- "UPDATE PurOrdDetail", "UPDATE PurOrdMaster",
- "INSERT INTO mdp_std_purchase_order\n", "INSERT INTO PurOrdDetail",
- })
- Assert.DoesNotContain(forbidden, code, StringComparison.Ordinal);
- }
- [Fact]
- public void 源侧只读()
- {
- var read = Between(Service(), "private async Task<List<SourceRow>> ReadSourceRowsAsync", "// ── 租户归属");
- Assert.Contains("GetDataTableAsync", read, StringComparison.Ordinal);
- foreach (var forbidden in new[] { "UPDATE ", "INSERT ", "DELETE ", "MERGE " })
- Assert.DoesNotContain(forbidden, read, StringComparison.Ordinal);
- }
- [Fact]
- public void 入账范围与推送口径一致()
- {
- var read = Between(Service(), "private async Task<List<SourceRow>> ReadSourceRowsAsync", "// ── 租户归属");
- Assert.Contains("Potype", read, StringComparison.Ordinal);
- Assert.Contains("_opt.SourcePotype", read, StringComparison.Ordinal);
- }
- // ── ⑧ 完成时间:当前无权威,不得落库 ────────────────────────────────────────
- [Fact]
- public void 不得创建completion_time列()
- {
- var sql = Migration("1.0.544.sql");
- foreach (var forbidden in new[] { "`completion_time`", "`completed_at`", "`procurement_completion_time`" })
- Assert.DoesNotContain(forbidden, sql, StringComparison.Ordinal);
- }
- [Fact]
- public void 不得把观测时间当完成时间()
- {
- var code = ServiceBody();
- Assert.DoesNotContain("completion_time", code, StringComparison.Ordinal);
- Assert.DoesNotContain("CompletionTime", code, StringComparison.Ordinal);
- }
- // ── ⑨ NOT_OBSERVED 不得物化 ─────────────────────────────────────────────────
- [Fact]
- public void 不得遍历SourceA反向造行()
- {
- var code = Service();
- // 驱动集合只能来自源侧读取结果
- Assert.Contains("foreach (var row in sourceRows)", code, StringComparison.Ordinal);
- // 本库采购行只用于校验与租户归属,不得成为写入驱动
- var run = Between(code, "var pending = new List<CompletionRow>", "// ④ UPSERT");
- Assert.DoesNotContain("foreach (var row in poLineIndex", run, StringComparison.Ordinal);
- // NOT_OBSERVED 是「不落行」的口头表达(注释里可以出现),
- // 但绝不能变成落库的字符串字面量、枚举值或列默认值
- Assert.DoesNotContain("\"NOT_OBSERVED\"", ServiceBody(), StringComparison.Ordinal);
- Assert.DoesNotContain("'NOT_OBSERVED'", Migration("1.0.544.sql"), StringComparison.Ordinal);
- }
- // ── ⑩ 调度与手动入口 ────────────────────────────────────────────────────────
- [Fact]
- public void 周期性全量对账必须存在且启用()
- {
- var job = Source("Job", "S3PoCompletionMdpSyncJob.cs");
- Assert.Contains("[JobDetail(\"job_s3_po_completion_mdp_sync\"", job, StringComparison.Ordinal);
- Assert.Contains("[Period(", job, StringComparison.Ordinal);
- Assert.Contains("RunFullAsync(0, \"AUTO\"", job, StringComparison.Ordinal);
- }
- [Fact]
- public void 手动刷新入口就位且不回显连接信息()
- {
- var ctrl = Source("Controllers", "AidopKanbanController.cs");
- Assert.Contains("[HttpPost(\"s3-po-completion-mdp/refresh\")]", ctrl, StringComparison.Ordinal);
- var block = Between(ctrl, "[HttpPost(\"s3-po-completion-mdp/refresh\")]", "staleRemoved = r.StaleRemoved");
- foreach (var forbidden in new[] { "ConnectionString", "Password", "db_user" })
- Assert.DoesNotContain(forbidden, block, StringComparison.OrdinalIgnoreCase);
- }
- [Fact]
- public void 跑批日志复用既有表与固定JobCode()
- {
- var code = Service();
- Assert.Contains("\"S3_PO_COMPLETION_MDP_SYNC\"", code, StringComparison.Ordinal);
- Assert.Contains("INSERT INTO mdp_transform_run_log", code, StringComparison.Ordinal);
- foreach (var s in new[] { "'RUNNING'", "status='SUCCESS'", "status='FAILED'" })
- Assert.Contains(s, code, StringComparison.Ordinal);
- }
- // ── ⑪ 上游批次资产不得回归 ──────────────────────────────────────────────────
- [Fact]
- public void IQC标准事实层与采购身份桥不得被本批改动()
- {
- var iqc = Migration("1.0.541.sql");
- Assert.Contains("UNIQUE KEY `uk_mdp_std_iqc_insp` (`tenant_id`, `source_row_id`)", iqc, StringComparison.Ordinal);
- var sql = Migration("1.0.544.sql");
- foreach (var forbidden in new[]
- {
- "ALTER TABLE `mdp_std_purchase_order`", "ALTER TABLE `mdp_std_iqc_inspection`",
- "UPDATE `mdp_std_purchase_order`", "DROP TABLE",
- })
- Assert.DoesNotContain(forbidden, sql, StringComparison.Ordinal);
- }
- }
|