From 6fd33341cf071b734e9303f349ce8097c8976e81 Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Wed, 18 Mar 2026 16:20:59 -0400 Subject: [PATCH] Remove Inbound Tracking option --- .claude/settings.local.json | 9 +- App/Controllers/Kit/KitController.asp | 163 +------- .../PurpleEnvelopeReportHelper.asp | 164 ++++++++ .../Kit/SwitchBoardPurpleEnvelopeEdit.asp | 2 - App/app.config.asp | 36 +- ImportService/TrackingDataImport.vbs | Bin 91684 -> 91684 bytes Tests/TestCase_PurpleEnvelopeReport.asp | 386 ++++++++++++++++++ Tests/Test_All.asp | 2 + Tests/TrackingDataImport_TestHarness.vbs | Bin 8629 -> 45984 bytes Tests/chillkat_serial | 1 + ...-spec-exportinkjetfile-integration-test.md | 300 ++++++++++++++ .../test-artifacts/automation-summary.md | 187 +++++++++ .../test-design-architecture.md | 248 +++++++++++ .../test-artifacts/test-design-progress.md | 80 ++++ _bmad-output/test-artifacts/test-design-qa.md | 311 ++++++++++++++ .../test-design/workspace-handoff.md | 85 ++++ 16 files changed, 1811 insertions(+), 163 deletions(-) create mode 100644 App/DomainModels/PurpleEnvelopeReportHelper.asp create mode 100644 Tests/TestCase_PurpleEnvelopeReport.asp create mode 100644 Tests/chillkat_serial create mode 100644 _bmad-output/implementation-artifacts/tech-spec-exportinkjetfile-integration-test.md create mode 100644 _bmad-output/test-artifacts/automation-summary.md create mode 100644 _bmad-output/test-artifacts/test-design-architecture.md create mode 100644 _bmad-output/test-artifacts/test-design-progress.md create mode 100644 _bmad-output/test-artifacts/test-design-qa.md create mode 100644 _bmad-output/test-artifacts/test-design/workspace-handoff.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 5e2afec..8f1f9e5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,14 @@ { "permissions": { "allow": [ - "Bash(powershell -Command \"Get-Content ''d:\\\\Development\\\\Tracking_Kits\\\\ImportService\\\\TrackingDataImport.vbs'' -Encoding Unicode | Out-String\")" + "Bash(powershell -Command \"Get-Content ''d:\\\\Development\\\\Tracking_Kits\\\\ImportService\\\\TrackingDataImport.vbs'' -Encoding Unicode | Out-String\")", + "Bash(git -C \"d:/Development/Tracking_Kits\" rev-parse HEAD)", + "Bash(cscript //NoLogo Tests/TrackingDataImport_TestHarness.vbs)", + "Bash(powershell -ExecutionPolicy Bypass -File \"d:\\\\Development\\\\Tracking_Kits\\\\Tests\\\\_fix_harness.ps1\")", + "Bash(powershell -Command \":*)", + "Bash(del \"d:\\\\Development\\\\Tracking_Kits\\\\Tests\\\\_fix_harness.ps1\")", + "Bash(powershell -ExecutionPolicy Bypass -File \"d:\\\\Development\\\\Tracking_Kits\\\\Tests\\\\_fix2.ps1\")", + "Bash(powershell -ExecutionPolicy Bypass -File \"d:\\\\Development\\\\Tracking_Kits\\\\Tests\\\\_fix3.ps1\")" ] } } diff --git a/App/Controllers/Kit/KitController.asp b/App/Controllers/Kit/KitController.asp index a76476e..8e2d13d 100644 --- a/App/Controllers/Kit/KitController.asp +++ b/App/Controllers/Kit/KitController.asp @@ -1,6 +1,7 @@ <% Option Explicit %> + <% Class KitController Public Model @@ -40,9 +41,7 @@ Class KitController dim ID : ID = Request.Form("Id") dim model : set model = KitRepository.FindByID(ID) set model = Automapper.AutoMap(Request.Form, model) - if Request.Form("InBoundTracking") = "on" Then - model.InboundSTID = SettingsRepository.FindByName("Inbound STID") - end if + model.InboundSTID = Null model.Status = "Ready to Assign Labels" @@ -99,10 +98,10 @@ Class KitController set Model.Kit = KitRepository.SwitchBoardPurpleEnvelopeEditFindById(id) set Model.StidDropDown = SettingsRepository.GetStidDropDownRS() set Model.ColorsDropDown = ColorsRepository.GetColorsDropDownRS() - set Model.Precincts = SortPrecinctColorRows(InkjetRecordsRepository.GetDistinctPrecinctsByKitId(id)) - set Model.PrecinctBallotRanges = SortPrecinctBallotRangeRows(InkjetRecordsRepository.GetPrecinctBallotRangesByKitId(id)) + set Model.Precincts = PurpleEnvelopeReportHelper().SortPrecinctColorRows(InkjetRecordsRepository.GetDistinctPrecinctsByKitId(id)) + set Model.PrecinctBallotRanges = PurpleEnvelopeReportHelper().SortPrecinctBallotRangeRows(InkjetRecordsRepository.GetPrecinctBallotRangesByKitId(id)) On Error Resume Next - Model.PurpleEnvelopeElectionLabel = FormatPurpleEnvelopeElectionLabel(SettingsRepository.FindByName("Electiondate")) + Model.PurpleEnvelopeElectionLabel = PurpleEnvelopeReportHelper().FormatElectionLabel(SettingsRepository.FindByName("Electiondate")) If Err.Number <> 0 Then Model.PurpleEnvelopeElectionLabel = "" Err.Clear @@ -114,158 +113,6 @@ Class KitController %> <% End Sub - Private Function FormatPurpleEnvelopeElectionLabel(ByVal rawValue) - FormatPurpleEnvelopeElectionLabel = Trim(rawValue & "") - If Len(FormatPurpleEnvelopeElectionLabel) = 0 Then Exit Function - - On Error Resume Next - dim parsedDate : parsedDate = CDate(rawValue) - If Err.Number = 0 Then - FormatPurpleEnvelopeElectionLabel = MonthName(Month(parsedDate), True) & "-" & CStr(Year(parsedDate)) - Else - Err.Clear - End If - On Error GoTo 0 - End Function - - Private Function SortPrecinctColorRows(ByVal rs) - dim list : set list = new LinkedList_Class - If rs.EOF Then - set SortPrecinctColorRows = list - Exit Function - End If - - dim items() - dim itemCount : itemCount = -1 - Do Until rs.EOF - itemCount = itemCount + 1 - ReDim Preserve items(itemCount) - - dim row : set row = new PrecinctColorRow_ViewModel_Class - row.PRECINCT = rs("PRECINCT") - If IsNull(rs("ColorId")) Then - row.ColorId = "" - Else - row.ColorId = rs("ColorId") - End If - Set items(itemCount) = row - rs.MoveNext - Loop - - SortPrecinctItems items - - dim i - For i = 0 To UBound(items) - list.Push items(i) - Next - - set SortPrecinctColorRows = list - End Function - - Private Function SortPrecinctBallotRangeRows(ByVal rs) - dim list : set list = new LinkedList_Class - If rs.EOF Then - set SortPrecinctBallotRangeRows = list - Exit Function - End If - - dim items() - dim itemCount : itemCount = -1 - Do Until rs.EOF - itemCount = itemCount + 1 - ReDim Preserve items(itemCount) - - dim row : set row = new PrecinctBallotRangeRow_ViewModel_Class - row.PRECINCT = rs("PRECINCT") - row.LowBallotNumber = rs("LowBallotNumber") - row.HighBallotNumber = rs("HighBallotNumber") - Set items(itemCount) = row - rs.MoveNext - Loop - - SortPrecinctItems items - - dim i - For i = 0 To UBound(items) - list.Push items(i) - Next - - set SortPrecinctBallotRangeRows = list - End Function - - Private Sub SortPrecinctItems(ByRef items) - If Not IsArray(items) Then Exit Sub - - dim i, j - For i = 0 To UBound(items) - 1 - For j = i + 1 To UBound(items) - If PrecinctSortsBefore(items(j).PRECINCT, items(i).PRECINCT) Then - dim temp : set temp = items(i) - Set items(i) = items(j) - Set items(j) = temp - End If - Next - Next - End Sub - - Private Function PrecinctSortsBefore(ByVal leftPrecinct, ByVal rightPrecinct) - dim leftType, leftNumber, leftSuffix, leftNormalized - dim rightType, rightNumber, rightSuffix, rightNormalized - - ParsePrecinctSortParts leftPrecinct, leftType, leftNumber, leftSuffix, leftNormalized - ParsePrecinctSortParts rightPrecinct, rightType, rightNumber, rightSuffix, rightNormalized - - If leftType <> rightType Then - PrecinctSortsBefore = (leftType < rightType) - Exit Function - End If - - If leftType = 0 Then - If leftNumber <> rightNumber Then - PrecinctSortsBefore = (leftNumber < rightNumber) - Exit Function - End If - - If leftSuffix <> rightSuffix Then - PrecinctSortsBefore = (leftSuffix < rightSuffix) - Exit Function - End If - End If - - If leftNormalized <> rightNormalized Then - PrecinctSortsBefore = (leftNormalized < rightNormalized) - Else - PrecinctSortsBefore = (UCase(Trim(leftPrecinct & "")) < UCase(Trim(rightPrecinct & ""))) - End If - End Function - - Private Sub ParsePrecinctSortParts(ByVal precinct, ByRef precinctType, ByRef numericPart, ByRef suffixPart, ByRef normalizedText) - dim rawPrecinct : rawPrecinct = Trim(precinct & "") - dim leadingDigits : leadingDigits = "" - dim i, currentChar - - For i = 1 To Len(rawPrecinct) - currentChar = Mid(rawPrecinct, i, 1) - If currentChar >= "0" And currentChar <= "9" Then - leadingDigits = leadingDigits & currentChar - Else - Exit For - End If - Next - - If Len(leadingDigits) > 0 Then - precinctType = 0 - numericPart = CLng(leadingDigits) - suffixPart = UCase(Trim(Mid(rawPrecinct, Len(leadingDigits) + 1))) - normalizedText = CStr(numericPart) & "|" & suffixPart - Else - precinctType = 1 - numericPart = 0 - suffixPart = UCase(rawPrecinct) - normalizedText = suffixPart - End If - End Sub - Public Sub Index dim page_size : page_size = 10 diff --git a/App/DomainModels/PurpleEnvelopeReportHelper.asp b/App/DomainModels/PurpleEnvelopeReportHelper.asp new file mode 100644 index 0000000..4dfaa79 --- /dev/null +++ b/App/DomainModels/PurpleEnvelopeReportHelper.asp @@ -0,0 +1,164 @@ +<% +Class PurpleEnvelopeReportHelper_Class + + Public Function FormatElectionLabel(ByVal rawValue) + FormatElectionLabel = Trim(rawValue & "") + If Len(FormatElectionLabel) = 0 Then Exit Function + + On Error Resume Next + dim parsedDate : parsedDate = CDate(rawValue) + If Err.Number = 0 Then + FormatElectionLabel = MonthName(Month(parsedDate), True) & "-" & CStr(Year(parsedDate)) + Else + Err.Clear + End If + On Error GoTo 0 + End Function + + Public Function SortPrecinctColorRows(ByVal rs) + dim list : set list = new LinkedList_Class + If rs.EOF Then + set SortPrecinctColorRows = list + Exit Function + End If + + dim items() + dim itemCount : itemCount = -1 + Do Until rs.EOF + itemCount = itemCount + 1 + ReDim Preserve items(itemCount) + + dim row : set row = new PrecinctColorRow_ViewModel_Class + row.PRECINCT = rs("PRECINCT") + If IsNull(rs("ColorId")) Then + row.ColorId = "" + Else + row.ColorId = rs("ColorId") + End If + Set items(itemCount) = row + rs.MoveNext + Loop + + SortPrecinctItems items + + dim i + For i = 0 To UBound(items) + list.Push items(i) + Next + + set SortPrecinctColorRows = list + End Function + + Public Function SortPrecinctBallotRangeRows(ByVal rs) + dim list : set list = new LinkedList_Class + If rs.EOF Then + set SortPrecinctBallotRangeRows = list + Exit Function + End If + + dim items() + dim itemCount : itemCount = -1 + Do Until rs.EOF + itemCount = itemCount + 1 + ReDim Preserve items(itemCount) + + dim row : set row = new PrecinctBallotRangeRow_ViewModel_Class + row.PRECINCT = rs("PRECINCT") + row.LowBallotNumber = rs("LowBallotNumber") + row.HighBallotNumber = rs("HighBallotNumber") + Set items(itemCount) = row + rs.MoveNext + Loop + + SortPrecinctItems items + + dim i + For i = 0 To UBound(items) + list.Push items(i) + Next + + set SortPrecinctBallotRangeRows = list + End Function + + Private Sub SortPrecinctItems(ByRef items) + If Not IsArray(items) Then Exit Sub + + dim i, j + For i = 0 To UBound(items) - 1 + For j = i + 1 To UBound(items) + If PrecinctSortsBefore(items(j).PRECINCT, items(i).PRECINCT) Then + dim temp : set temp = items(i) + Set items(i) = items(j) + Set items(j) = temp + End If + Next + Next + End Sub + + Private Function PrecinctSortsBefore(ByVal leftPrecinct, ByVal rightPrecinct) + dim leftType, leftNumber, leftSuffix, leftNormalized + dim rightType, rightNumber, rightSuffix, rightNormalized + + ParsePrecinctSortParts leftPrecinct, leftType, leftNumber, leftSuffix, leftNormalized + ParsePrecinctSortParts rightPrecinct, rightType, rightNumber, rightSuffix, rightNormalized + + If leftType <> rightType Then + PrecinctSortsBefore = (leftType < rightType) + Exit Function + End If + + If leftType = 0 Then + If leftNumber <> rightNumber Then + PrecinctSortsBefore = (leftNumber < rightNumber) + Exit Function + End If + + If leftSuffix <> rightSuffix Then + PrecinctSortsBefore = (leftSuffix < rightSuffix) + Exit Function + End If + End If + + If leftNormalized <> rightNormalized Then + PrecinctSortsBefore = (leftNormalized < rightNormalized) + Else + PrecinctSortsBefore = (UCase(Trim(leftPrecinct & "")) < UCase(Trim(rightPrecinct & ""))) + End If + End Function + + Private Sub ParsePrecinctSortParts(ByVal precinct, ByRef precinctType, ByRef numericPart, ByRef suffixPart, ByRef normalizedText) + dim rawPrecinct : rawPrecinct = Trim(precinct & "") + dim leadingDigits : leadingDigits = "" + dim i, currentChar + + For i = 1 To Len(rawPrecinct) + currentChar = Mid(rawPrecinct, i, 1) + If currentChar >= "0" And currentChar <= "9" Then + leadingDigits = leadingDigits & currentChar + Else + Exit For + End If + Next + + If Len(leadingDigits) > 0 Then + precinctType = 0 + numericPart = CLng(leadingDigits) + suffixPart = UCase(Trim(Mid(rawPrecinct, Len(leadingDigits) + 1))) + normalizedText = CStr(numericPart) & "|" & suffixPart + Else + precinctType = 1 + numericPart = 0 + suffixPart = UCase(rawPrecinct) + normalizedText = suffixPart + End If + End Sub +End Class + +dim PurpleEnvelopeReportHelper__Singleton +Function PurpleEnvelopeReportHelper() + If IsEmpty(PurpleEnvelopeReportHelper__Singleton) Then + set PurpleEnvelopeReportHelper__Singleton = new PurpleEnvelopeReportHelper_Class + End If + set PurpleEnvelopeReportHelper = PurpleEnvelopeReportHelper__Singleton +End Function +%> diff --git a/App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp b/App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp index 6b47c56..3c60c34 100644 --- a/App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp +++ b/App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp @@ -105,8 +105,6 @@ <%= HTML.Hidden("Id", Model.Kit.ID) %> <%= HTML.DropDownListExt("OutboundSTID","hmm",Model.StidDropDown,"STID","OPTION",Array("Class","form-select")) %>

- <%= HTML.Checkbox("InBoundTracking",0) %>Inbound Tracking -

<%= HTML.Button("submit", " Save", "btn-primary") %>

diff --git a/App/app.config.asp b/App/app.config.asp index 793dff3..c7c877a 100644 --- a/App/app.config.asp +++ b/App/app.config.asp @@ -18,12 +18,44 @@ select case dev ExportDirectory ="\\kci-syn-cl01\PC Transfer\TrackingDataExport\" end select + +Function LoadChilkatSerial() + dim fallbackSerial : fallbackSerial = "KENTCM.CB1022025_RGzBPM5J655e" + LoadChilkatSerial = fallbackSerial + + On Error Resume Next + dim fso : set fso = Server.CreateObject("Scripting.FileSystemObject") + dim serialPath : serialPath = Request.ServerVariables("APPL_PHYSICAL_PATH") & "Tests\chillkat_serial" + + If Err.Number = 0 Then + If fso.FileExists(serialPath) Then + dim serialFile : set serialFile = fso.OpenTextFile(serialPath, 1, False) + If Err.Number = 0 Then + dim fileSerial : fileSerial = Trim(serialFile.ReadAll()) + serialFile.Close + set serialFile = Nothing + + If Len(fileSerial) > 0 Then + LoadChilkatSerial = fileSerial + End If + Else + Err.Clear + End If + End If + Else + Err.Clear + End If + + set fso = Nothing + On Error GoTo 0 +End Function + '======================================================================================================================= ' Set Global Variables Here '======================================================================================================================= dim glob:set glob = Server.CreateObject("Chilkat_9_5_0.Global") -dim success:success = glob.UnlockBundle("KENTCM.CB1022025_RGzBPM5J655e") +dim success:success = glob.UnlockBundle(LoadChilkatSerial()) If (success <> 1) Then put(glob.LastErrorText) End If -%> \ No newline at end of file +%> diff --git a/ImportService/TrackingDataImport.vbs b/ImportService/TrackingDataImport.vbs index d16e8f798ffe25eb6d70619a84d65a790b9867be..72991801f16bad44e1f493274745cceb9269ac86 100644 GIT binary patch delta 50 ucmZ2-hIPpq)(!JG8C@pN=iCVT?VA@HBaH$K81&ILL>m0w-D3- diff --git a/Tests/TestCase_PurpleEnvelopeReport.asp b/Tests/TestCase_PurpleEnvelopeReport.asp new file mode 100644 index 0000000..9512d0a --- /dev/null +++ b/Tests/TestCase_PurpleEnvelopeReport.asp @@ -0,0 +1,386 @@ + + + + + + +<% +Class PurpleEnvelopeReport_Tests + Private m_tempDbPath + + Public Sub Setup + m_tempDbPath = CreateDisposableDatabaseCopy() + UseDisposableDatabase m_tempDbPath + End Sub + + Public Sub Teardown + ResetDisposableDatabase + End Sub + + Public Function TestCaseNames + TestCaseNames = Array( _ + "Test_FormatElectionLabel_Returns_Mon_YYYY_For_Valid_Date", _ + "Test_FormatElectionLabel_Returns_Trimmed_Raw_Value_For_Invalid_Date", _ + "Test_SortPrecinctColorRows_Handles_Zero_Padded_And_Lettered_Precincts", _ + "Test_SortPrecinctBallotRangeRows_Preserves_Ranges_While_Sorting", _ + "Test_GetPrecinctBallotRangesByKitId_Uses_Seeded_Data", _ + "Test_UpdateColorForKit_Updates_All_Seeded_Rows_For_The_Target_Kit", _ + "Test_UpdateColorForPrecinct_Updates_Only_The_Targeted_Precinct", _ + "Test_SwitchBoardPurpleEnvelopeEditFindById_Returns_Seeded_Header_Data", _ + "Test_KitController_Post_Actions_Still_Delegate_To_Color_Update_Repositories", _ + "Test_Report_View_Keeps_Print_Only_CSS_Contract", _ + "Test_Report_View_Keeps_No_Data_Row_And_Page_Spacer" _ + ) + End Function + + Private Sub Destroy(ByRef o) + On Error Resume Next + o.Close + On Error GoTo 0 + Set o = Nothing + End Sub + + Private Function CreatePrecinctColorRecordset(ByVal rows) + dim rs : set rs = Server.CreateObject("ADODB.Recordset") + With rs.Fields + .Append "PRECINCT", adVarChar, 50 + .Append "ColorId", adVarChar, 50 + End With + + rs.Open + + dim i + For i = 0 To UBound(rows) + rs.AddNew + rs("PRECINCT") = rows(i)(0) + If IsNull(rows(i)(1)) Then + rs("ColorId") = Null + Else + rs("ColorId") = rows(i)(1) + End If + rs.Update + Next + + rs.MoveFirst + set CreatePrecinctColorRecordset = rs + End Function + + Private Function CreateBallotRangeRecordset(ByVal rows) + dim rs : set rs = Server.CreateObject("ADODB.Recordset") + With rs.Fields + .Append "PRECINCT", adVarChar, 50 + .Append "LowBallotNumber", adInteger + .Append "HighBallotNumber", adInteger + End With + + rs.Open + + dim i + For i = 0 To UBound(rows) + rs.AddNew + rs("PRECINCT") = rows(i)(0) + rs("LowBallotNumber") = rows(i)(1) + rs("HighBallotNumber") = rows(i)(2) + rs.Update + Next + + rs.MoveFirst + set CreateBallotRangeRecordset = rs + End Function + + Private Function PrecinctListToCsv(ByVal list) + dim values() + dim idx : idx = -1 + dim it : set it = list.Iterator() + dim row + + Do While it.HasNext + set row = it.GetNext() + idx = idx + 1 + ReDim Preserve values(idx) + values(idx) = row.PRECINCT + Loop + + If idx = -1 Then + PrecinctListToCsv = "" + Else + PrecinctListToCsv = Join(values, ",") + End If + End Function + + Private Function ReadAllText(ByVal relativePath) + dim fso : set fso = Server.CreateObject("Scripting.FileSystemObject") + dim fileHandle : set fileHandle = fso.OpenTextFile(Server.MapPath(relativePath), 1, False) + ReadAllText = fileHandle.ReadAll() + fileHandle.Close + Set fileHandle = Nothing + Set fso = Nothing + End Function + + Private Function CreateDisposableDatabaseCopy() + dim fso : set fso = Server.CreateObject("Scripting.FileSystemObject") + dim tempFolderPath : tempFolderPath = Server.MapPath("Temp") + If Not fso.FolderExists(tempFolderPath) Then + fso.CreateFolder tempFolderPath + End If + + dim sourcePath : sourcePath = Server.MapPath("../Data/webdata - Copy.mdb") + dim guidValue : guidValue = Mid(CreateObject("Scriptlet.TypeLib").Guid, 2, 36) + guidValue = Replace(guidValue, "-", "") + CreateDisposableDatabaseCopy = tempFolderPath & "\purple-envelope-tests-" & guidValue & ".mdb" + fso.CopyFile sourcePath, CreateDisposableDatabaseCopy, True + + Set fso = Nothing + End Function + + Private Sub UseDisposableDatabase(ByVal dbPath) + Set DAL__Singleton = Nothing + set DAL__Singleton = new Database_Class + DAL__Singleton.Initialize "Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data Source=" & dbPath & ";" + End Sub + + Private Sub ResetDisposableDatabase() + dim tempPath : tempPath = m_tempDbPath + Set DAL__Singleton = Nothing + + If Len(tempPath) > 0 Then + dim fso : set fso = Server.CreateObject("Scripting.FileSystemObject") + If fso.FileExists(tempPath) Then + fso.DeleteFile tempPath, True + End If + Set fso = Nothing + End If + + m_tempDbPath = "" + End Sub + + Private Function NextSeedKey(ByVal prefix) + NextSeedKey = prefix & Replace(Replace(Replace(CStr(Now()), "/", ""), ":", ""), " ", "") & CStr(Int((Rnd() * 1000000) + 1)) + End Function + + Private Function NextJurisdictionCode() + dim guidValue : guidValue = Mid(CreateObject("Scriptlet.TypeLib").Guid, 2, 8) + NextJurisdictionCode = "J" & Replace(guidValue, "-", "") + End Function + + Private Function SeedJurisdiction(ByVal jCode, ByVal name) + DAL.Execute "INSERT INTO [Jurisdiction] ([JCode], [Name], [Mailing_Address], [CSZ], [IMB], [IMB_Digits]) VALUES (?,?,?,?,?,?)", _ + Array(jCode, name, "123 Test St", "Lansing, MI 48933", "IMB", "123456789") + SeedJurisdiction = jCode + End Function + + Private Function SeedPurpleEnvelopeKit(ByVal jobNumber, ByVal jCode, ByVal status) + DAL.Execute "INSERT INTO [Kit] ([JobNumber], [Jcode], [CreatedOn], [InkJetJob], [JobType], [Cass], [Status], [OutboundSTID], [InboundSTID], [OfficeCopiesAmount]) VALUES (?,?,?,?,?,?,?,?,?,?)", _ + Array(jobNumber, jCode, Now(), False, "Purple Envelopes", False, status, "", "", Null) + + SeedPurpleEnvelopeKit = QueryScalar("SELECT TOP 1 [ID] FROM [Kit] WHERE [JobNumber] = ? ORDER BY [ID] DESC", jobNumber) + End Function + + Private Sub SeedInkjetRecord(ByVal kitId, ByVal precinct, ByVal ballotNumber, ByVal colorId) + DAL.Execute "INSERT INTO [InkjetRecords] ([KitID], [PRECINCT], [BALLOT_NUMBER], [ColorId]) VALUES (?,?,?,?)", _ + Array(kitId, precinct, ballotNumber, colorId) + End Sub + + Private Function QueryScalar(ByVal sql, ByVal params) + dim rs : set rs = DAL.Query(sql, params) + QueryScalar = rs(0).Value + Destroy rs + End Function + + Private Function QueryPrecinctColorMap(ByVal kitId) + dim map : set map = Server.CreateObject("Scripting.Dictionary") + dim rs : set rs = DAL.Query("SELECT [PRECINCT], [ColorId] FROM [InkjetRecords] WHERE [KitID] = ? ORDER BY [PRECINCT]", kitId) + + Do Until rs.EOF + If IsNull(rs("ColorId")) Then + map(rs("PRECINCT") & "") = "" + Else + map(rs("PRECINCT") & "") = CStr(rs("ColorId")) + End If + rs.MoveNext + Loop + + Destroy rs + set QueryPrecinctColorMap = map + End Function + + Public Sub Test_FormatElectionLabel_Returns_Mon_YYYY_For_Valid_Date(T) + T.AssertEqual "May-2026", PurpleEnvelopeReportHelper().FormatElectionLabel("5/26/2026"), "Expected valid dates to render as Mon-YYYY." + End Sub + + Public Sub Test_FormatElectionLabel_Returns_Trimmed_Raw_Value_For_Invalid_Date(T) + T.AssertEqual "not a date", PurpleEnvelopeReportHelper().FormatElectionLabel(" not a date "), "Expected invalid election dates to fall back to trimmed raw text." + T.AssertEqual "", PurpleEnvelopeReportHelper().FormatElectionLabel(""), "Expected empty election dates to stay empty." + End Sub + + Public Sub Test_SortPrecinctColorRows_Handles_Zero_Padded_And_Lettered_Precincts(T) + dim rs : set rs = CreatePrecinctColorRecordset(Array( _ + Array("12B", "2"), _ + Array("0003", ""), _ + Array("A1", "4"), _ + Array("12A", "3"), _ + Array("3", "5"), _ + Array("0001", "1"), _ + Array("12", "6") _ + )) + dim sorted : set sorted = PurpleEnvelopeReportHelper().SortPrecinctColorRows(rs) + + T.AssertEqual "0001,0003,3,12,12A,12B,A1", PrecinctListToCsv(sorted), "Expected mixed-format precincts to sort in the current ascending order." + + Destroy rs + Destroy sorted + End Sub + + Public Sub Test_SortPrecinctBallotRangeRows_Preserves_Ranges_While_Sorting(T) + dim rs : set rs = CreateBallotRangeRecordset(Array( _ + Array("12A", 20, 29), _ + Array("0003", 1, 10), _ + Array("12", 11, 19), _ + Array("A1", 30, 39) _ + )) + dim sorted : set sorted = PurpleEnvelopeReportHelper().SortPrecinctBallotRangeRows(rs) + dim it : set it = sorted.Iterator() + dim row + + T.AssertEqual "0003,12,12A,A1", PrecinctListToCsv(sorted), "Expected report rows to follow the same precinct order as the color table." + + set row = it.GetNext() + T.AssertEqual 1, row.LowBallotNumber, "Expected the low ballot number to stay attached to precinct 0003." + T.AssertEqual 10, row.HighBallotNumber, "Expected the high ballot number to stay attached to precinct 0003." + + Destroy rs + Destroy sorted + End Sub + + Public Sub Test_GetPrecinctBallotRangesByKitId_Uses_Seeded_Data(T) + Randomize + dim jCode : jCode = NextJurisdictionCode() + dim jobNumber : jobNumber = NextSeedKey("JOB") + Call SeedJurisdiction(jCode, "City of Lansing") + dim kitId : kitId = SeedPurpleEnvelopeKit(jobNumber, jCode, "Ready To Assign STIDS") + + SeedInkjetRecord kitId, "12A", "29", Null + SeedInkjetRecord kitId, "0003", "10", Null + SeedInkjetRecord kitId, "12A", "20", Null + SeedInkjetRecord kitId, "0003", "1", Null + SeedInkjetRecord kitId, "A1", "30", Null + + dim rs : set rs = InkjetRecordsRepository.GetPrecinctBallotRangesByKitId(kitId) + dim sorted : set sorted = PurpleEnvelopeReportHelper().SortPrecinctBallotRangeRows(rs) + dim it : set it = sorted.Iterator() + dim firstRow, secondRow, thirdRow + + set firstRow = it.GetNext() + set secondRow = it.GetNext() + set thirdRow = it.GetNext() + + T.AssertEqual "0003,12A,A1", PrecinctListToCsv(sorted), "Expected seeded ballot ranges to sort in the current report order." + T.AssertEqual 1, firstRow.LowBallotNumber, "Expected precinct 0003 to use the seeded minimum ballot number." + T.AssertEqual 10, firstRow.HighBallotNumber, "Expected precinct 0003 to use the seeded maximum ballot number." + T.AssertEqual 20, secondRow.LowBallotNumber, "Expected precinct 12A to use the seeded minimum ballot number." + T.AssertEqual 29, secondRow.HighBallotNumber, "Expected precinct 12A to use the seeded maximum ballot number." + T.AssertEqual 30, thirdRow.LowBallotNumber, "Expected single-row precincts to keep their ballot number as the low value." + T.AssertEqual 30, thirdRow.HighBallotNumber, "Expected single-row precincts to keep their ballot number as the high value." + + Destroy rs + Destroy sorted + Set firstRow = Nothing + Set secondRow = Nothing + Set thirdRow = Nothing + End Sub + + Public Sub Test_UpdateColorForKit_Updates_All_Seeded_Rows_For_The_Target_Kit(T) + Randomize + dim jCode : jCode = NextJurisdictionCode() + Call SeedJurisdiction(jCode, "City of Lansing") + + dim targetKitId : targetKitId = SeedPurpleEnvelopeKit(NextSeedKey("JOB"), jCode, "Ready To Assign STIDS") + dim otherKitId : otherKitId = SeedPurpleEnvelopeKit(NextSeedKey("JOB"), jCode, "Ready To Assign STIDS") + + SeedInkjetRecord targetKitId, "0003", "1", 1 + SeedInkjetRecord targetKitId, "12A", "2", Null + SeedInkjetRecord otherKitId, "9", "3", 4 + + InkjetRecordsRepository.UpdateColorForKit targetKitId, 7 + + dim targetMap : set targetMap = QueryPrecinctColorMap(targetKitId) + dim otherMap : set otherMap = QueryPrecinctColorMap(otherKitId) + + T.AssertEqual "7", targetMap("0003"), "Expected kit-wide color updates to touch every row in the targeted kit." + T.AssertEqual "7", targetMap("12A"), "Expected kit-wide color updates to touch every row in the targeted kit." + T.AssertEqual "4", otherMap("9"), "Expected kit-wide color updates to leave other kits unchanged." + + Set targetMap = Nothing + Set otherMap = Nothing + End Sub + + Public Sub Test_UpdateColorForPrecinct_Updates_Only_The_Targeted_Precinct(T) + Randomize + dim jCode : jCode = NextJurisdictionCode() + Call SeedJurisdiction(jCode, "City of Lansing") + + dim kitId : kitId = SeedPurpleEnvelopeKit(NextSeedKey("JOB"), jCode, "Ready To Assign STIDS") + + SeedInkjetRecord kitId, "0003", "1", 1 + SeedInkjetRecord kitId, "12A", "2", 2 + SeedInkjetRecord kitId, "A1", "3", Null + + InkjetRecordsRepository.UpdateColorForPrecinct kitId, "12A", 9 + + dim colorMap : set colorMap = QueryPrecinctColorMap(kitId) + + T.AssertEqual "1", colorMap("0003"), "Expected non-targeted precincts to keep their original color." + T.AssertEqual "9", colorMap("12A"), "Expected the targeted precinct to receive the new color." + T.AssertEqual "", colorMap("A1"), "Expected non-targeted blank colors to remain blank." + + Set colorMap = Nothing + End Sub + + Public Sub Test_SwitchBoardPurpleEnvelopeEditFindById_Returns_Seeded_Header_Data(T) + Randomize + dim jCode : jCode = NextJurisdictionCode() + dim jobNumber : jobNumber = NextSeedKey("JOB") + Call SeedJurisdiction(jCode, "City of Lansing") + dim kitId : kitId = SeedPurpleEnvelopeKit(jobNumber, jCode, "Ready To Assign STIDS") + + SeedInkjetRecord kitId, "0003", "1", Null + SeedInkjetRecord kitId, "12A", "2", Null + + dim model : set model = KitRepository.SwitchBoardPurpleEnvelopeEditFindById(kitId) + + T.AssertEqual kitId, model.ID, "Expected the seeded purple-envelope kit to be returned." + T.AssertEqual jobNumber, model.JobNumber, "Expected the seeded job number to be returned." + T.AssertEqual jCode, model.JCode, "Expected the seeded jurisdiction code to be returned." + T.AssertEqual "City of Lansing", model.Jurisdiction, "Expected the seeded jurisdiction name to be returned." + T.AssertEqual 2, model.LabelCount, "Expected the seeded inkjet row count to flow into the label count." + T.AssertEqual "Ready To Assign STIDS", model.Status, "Expected the seeded status to be returned." + + Set model = Nothing + End Sub + + Public Sub Test_KitController_Post_Actions_Still_Delegate_To_Color_Update_Repositories(T) + dim controllerSource : controllerSource = ReadAllText("../App/Controllers/Kit/KitController.asp") + + T.Assert InStr(controllerSource, "Public Sub AssignKitColorPost") > 0, "Expected the kit-wide color POST action to remain present." + T.Assert InStr(controllerSource, "InkjetRecordsRepository.UpdateColorForKit CLng(ID), CLng(Request.Form(""KitColorId""))") > 0, "Expected AssignKitColorPost to keep delegating to the kit-wide color repository update." + T.Assert InStr(controllerSource, "Public Sub AssignPrecinctColorsPost") > 0, "Expected the precinct color POST action to remain present." + T.Assert InStr(controllerSource, "InkjetRecordsRepository.UpdateColorForPrecinct CLng(ID), precinct, CLng(colorId)") > 0, "Expected AssignPrecinctColorsPost to keep delegating to the precinct-specific color repository update." + End Sub + + Public Sub Test_Report_View_Keeps_Print_Only_CSS_Contract(T) + dim viewSource : viewSource = ReadAllText("../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp") + + T.Assert InStr(viewSource, "id=""purple-envelope-report-print""") > 0, "Expected the report print wrapper id to remain in the view." + T.Assert InStr(viewSource, "body * {") > 0, "Expected print CSS to hide non-report content." + T.Assert InStr(viewSource, "font-size: 10pt;") > 0, "Expected the reduced report font size to remain unchanged." + T.Assert InStr(viewSource, "padding: 0.45in 0.25in 0.25in 0.25in;") > 0, "Expected the print top buffer padding to remain unchanged." + End Sub + + Public Sub Test_Report_View_Keeps_No_Data_Row_And_Page_Spacer(T) + dim viewSource : viewSource = ReadAllText("../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp") + + T.Assert InStr(viewSource, "class=""print-page-spacer""") > 0, "Expected the repeated-page spacer row to remain in the report header." + T.Assert InStr(viewSource, "No precinct ballot data found for this kit.") > 0, "Expected the empty-state report message to remain unchanged." + End Sub +End Class +%> diff --git a/Tests/Test_All.asp b/Tests/Test_All.asp index ad7ca59..a2ea131 100644 --- a/Tests/Test_All.asp +++ b/Tests/Test_All.asp @@ -9,6 +9,7 @@ Option Explicit + <% 'Used in some of the test case classes included into this file. @@ -26,5 +27,6 @@ Runner.AddTestContainer new AutoMap_Tests Runner.AddTestContainer new FlexMap_Tests Runner.AddTestContainer new DynMap_Tests Runner.AddTestContainer new StringBuilder_Tests +Runner.AddTestContainer new PurpleEnvelopeReport_Tests Runner.Display %> diff --git a/Tests/TrackingDataImport_TestHarness.vbs b/Tests/TrackingDataImport_TestHarness.vbs index ac83cdb8ff08667aec56cd82d84a545d9441c55e..2115188b5d202a44bfa7d977dc3e316632df2fce 100644 GIT binary patch literal 45984 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 Tb@1z{Cb>XH@x4%dO4k1eq(p%8 literal 8629 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 F{1 0 is present) +- Full row-by-row validation (10-record spot check is sufficient) +- Restoring Kit.Status after test (MDB copy is disposable; original `webdata.mdb` is untouched) + +--- + +## Context for Development + +### Codebase Patterns + +- **Harness pattern**: `LoadFunctions(filePath, functionNames)` extracts named functions from source via text parsing; `ExecuteGlobal` makes them available in harness global scope. Global vars set before calling functions are visible inside loaded functions. +- **`LoadFunctions` is called at line 34**, before any test code. Adding `"GetSetting"` and `"ExportInkjetFile"` to the `functionNames` array (lines 16–32) is sufficient — they'll be extracted and available globally. +- **`Set objFSO = fso` at line 36** — already set. `ExportInkjetFile` uses `objFSO` internally; this is already satisfied. +- **`chilkatAvailable` declared at line 39** — already exists. New `integrationDbAvailable` follows the same declare-in-preamble, set-in-init-block pattern. +- **Assertion API**: The harness has **no `Assert` sub** — only `AssertEqual(actual, expected, label)` and `AssertArrayEqual`. All integration test assertions must use `AssertEqual condition, True, "label"` form. +- **DB dependency chain**: `ExportInkjetFile` calls `oConn.Open(ConnectionString)` if `oConn.State = 0`. It manages its own connection lifecycle. The harness must set `oConn` (via `Set oConn = CreateObject("ADODB.Connection")`) and `ConnectionString` as globals before calling. +- **`ExportDirectory` global**: Used by `ExportInkjetFile` as-is (no trailing slash added internally). Must include trailing `\` in the harness assignment. +- **Chilkat CSV write-back verification**: `ExportInkjetFile` creates its own internal `Chilkat_9_5_0.Csv` object. Chilkat unlock is process-wide — already done in harness init block. Read-back uses a separate object. + +### Files to Reference + +| File | Purpose | +| ---- | ------- | +| `Tests/TrackingDataImport_TestHarness.vbs` | Target file — 3 insertion points: globals (after line 15), `functionNames` array (after line 31), init block (after line 69), test block (before line 206) | +| `ImportService/TrackingDataImport.vbs` | Source — `ExportInkjetFile` at line 251 (Function), `GetSetting` also in same file | +| `Data/webdata - Copy.mdb` | MDB fixture — real Kit + InkjetRecords + KitLabels + Colors + Jurisdiction + Contacts + Settings | + +### Technical Decisions + +- **Use `Data/webdata - Copy.mdb` directly**: It's already a copy and contains real data. No need to seed a new fixture. The UPDATE side effect (Kit.Status='Done') is acceptable since this MDB is a disposable copy. +- **Discover KitID at runtime via JOIN**: Query `SELECT TOP 1 ir.KitID FROM ((InkjetRecords ir INNER JOIN Kit k ON ir.KitID = k.ID) INNER JOIN Jurisdiction j ON k.JCode = j.JCode) INNER JOIN Contacts c ON k.JCode = c.JURISCODE` to ensure discovered KitID has all required related records. Avoid orphan-crash on `JurisdictionRs` or `ContactRs` inside the function. +- **ExportDirectory as temp path**: Set to `fso.BuildPath(scriptDir, "export-test-output")`. **Delete before call** (not just after) to ensure no stale CSV from a prior failed run contaminates assertions. +- **ADODB driver probe**: Try `Microsoft.ACE.OLEDB.12.0` first; fall back to `Microsoft.Jet.OLEDB.4.0`. If both fail, skip with message. Guard with `fso.FileExists(integrationMdbPath)` before attempting open. +- **Load both `GetSetting` and `ExportInkjetFile`** in `functionNames` array — `GetSetting` is a DB-backed dependency called internally by `ExportInkjetFile`. +- **Chilkat read-back**: Capture `verifyCsv.LoadFile(csvPath)` return value and assert it before proceeding to column/row assertions — prevents vacuously-passing assertions on a failed load. +- **Cleanup is error-suppressed**: Wrap `fso.DeleteFolder` in `On Error Resume Next` / `On Error GoTo 0` — AV scanners may briefly hold a handle on the new CSV; a cleanup failure should not fail the test. + +--- + +## Implementation Plan + +### Tasks + +- [x] **Task 1: Add integration test globals and ADODB probe block** + - File: `Tests/TrackingDataImport_TestHarness.vbs` + - Action: Insert 7 `Dim` declarations after line 15; insert ADODB probe block after line 69 + - Notes: Sets `integrationDbAvailable`/`integrationDbSkipReason` — same guard pattern as `chilkatAvailable` + +**Insertion point: after line 15** (`Dim DataDirectory`). Add: + +```vbscript +Dim oConn +Dim ConnectionString +Dim ExportDirectory +Dim integrationMdbPath +Dim integrationExportDir +Dim integrationDbAvailable +Dim integrationDbSkipReason +``` + +**Insertion point: after line 69** (`On Error GoTo 0` at end of Chilkat init block). Add ADODB probe block: + +```vbscript +integrationMdbPath = fso.BuildPath(scriptDir, "..\Data\webdata - Copy.mdb") +integrationExportDir = fso.BuildPath(scriptDir, "export-test-output") +integrationDbAvailable = False +integrationDbSkipReason = "" +If Not fso.FileExists(integrationMdbPath) Then + integrationDbSkipReason = "MDB fixture not found: " & integrationMdbPath +Else + Set oConn = CreateObject("ADODB.Connection") + On Error Resume Next + oConn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & integrationMdbPath & ";" + If Err.Number <> 0 Then + Err.Clear + oConn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & integrationMdbPath & ";" + End If + If Err.Number <> 0 Then + integrationDbSkipReason = "No ADODB provider available (ACE and Jet both failed): " & Err.Description + Err.Clear + Else + integrationDbAvailable = True + ConnectionString = oConn.ConnectionString + ExportDirectory = integrationExportDir & "\" + End If + On Error GoTo 0 + If oConn.State = 1 Then oConn.Close +End If +``` + +- [x] **Task 2: Add `GetSetting` and `ExportInkjetFile` to `functionNames` array** + - File: `Tests/TrackingDataImport_TestHarness.vbs` + - Action: Extend array at lines 30–31 to include two new entries + - Notes: Must be done before `LoadFunctions` call at line 34; order within array does not matter + +**Insertion point: lines 30–31**. Change: +```vbscript + "CheckStringDoesNotHaveForiegnCountries", _ + "ValidImportCSV" _ +``` +To: +```vbscript + "CheckStringDoesNotHaveForiegnCountries", _ + "ValidImportCSV", _ + "GetSetting", _ + "ExportInkjetFile" _ +``` + +- [x] **Task 3: Add ExportInkjetFile integration test block** + - File: `Tests/TrackingDataImport_TestHarness.vbs` + - Action: Insert full integration test block before line 206 (`WScript.Echo ""`) + - Notes: Guarded by `chilkatAvailable` AND `integrationDbAvailable`; uses `AssertEqual` throughout (no `Assert` sub exists); pre-call dir cleanup, post-call DB side-effect check, error-suppressed cleanup + +**Insertion point: before line 206** (`WScript.Echo ""`). Add: + +```vbscript +' === ExportInkjetFile Integration Test === +If Not chilkatAvailable Then + WScript.Echo "SKIP: ExportInkjetFile integration test (Chilkat unavailable)" +ElseIf Not integrationDbAvailable Then + WScript.Echo "SKIP: ExportInkjetFile integration test (" & integrationDbSkipReason & ")" +Else + oConn.Open ConnectionString + + Dim kitDiscoverRs + Set kitDiscoverRs = oConn.Execute( _ + "SELECT TOP 1 ir.KitID FROM ((InkjetRecords ir " & _ + "INNER JOIN Kit k ON ir.KitID = k.ID) " & _ + "INNER JOIN Jurisdiction j ON k.JCode = j.JCode) " & _ + "INNER JOIN Contacts c ON k.JCode = c.JURISCODE;") + If kitDiscoverRs.EOF Then + WScript.Echo "SKIP: ExportInkjetFile — no complete Kit+InkjetRecords+Jurisdiction+Contact found in fixture MDB" + oConn.Close + Else + Dim testKitID : testKitID = kitDiscoverRs("KitID").Value + + Dim countRs + Set countRs = oConn.Execute("SELECT COUNT(*) AS N FROM InkjetRecords WHERE KitID=" & testKitID & ";") + Dim expectedRows : expectedRows = countRs("N").Value + + Dim kitRsCheck + Set kitRsCheck = oConn.Execute("SELECT JCode FROM Kit WHERE ID=" & testKitID & ";") + Dim testJCode : testJCode = kitRsCheck("JCode").Value + + oConn.Close + + ' Delete stale output before call, then recreate + On Error Resume Next + If fso.FolderExists(integrationExportDir) Then fso.DeleteFolder(integrationExportDir, True) + On Error GoTo 0 + fso.CreateFolder integrationExportDir + + ExportInkjetFile testKitID + + Dim exportSubfolders : Set exportSubfolders = fso.GetFolder(integrationExportDir).SubFolders + Dim csvFound : csvFound = False + Dim csvPath : csvPath = "" + Dim sf + For Each sf In exportSubfolders + Dim csvFiles : Set csvFiles = sf.Files + Dim f + For Each f In csvFiles + If LCase(fso.GetExtensionName(f.Name)) = "csv" Then + csvFound = True + csvPath = f.Path + End If + Next + Next + + AssertEqual csvFound, True, "[INT] ExportInkjetFile: CSV file created" + + If csvFound Then + Dim verifyCsv : Set verifyCsv = CreateObject("Chilkat_9_5_0.Csv") + verifyCsv.HasColumnNames = 1 + Dim csvLoaded : csvLoaded = verifyCsv.LoadFile(csvPath) + AssertEqual csvLoaded, True, "[INT] ExportInkjetFile: CSV loaded by Chilkat" + + If csvLoaded Then + AssertEqual verifyCsv.NumColumns, 22, "[INT] ExportInkjetFile: 22 columns" + AssertEqual verifyCsv.ColumnName(0), "Full Name", "[INT] ExportInkjetFile: col 0 = Full Name" + AssertEqual verifyCsv.ColumnName(5), "IM barcode Characters", "[INT] ExportInkjetFile: col 5 = IM barcode Characters" + AssertEqual verifyCsv.ColumnName(8), "Ballot Number", "[INT] ExportInkjetFile: col 8 = Ballot Number" + AssertEqual verifyCsv.ColumnName(11), "Combined Pct_Ballot Num", "[INT] ExportInkjetFile: col 11 = Combined Pct_Ballot Num" + AssertEqual verifyCsv.ColumnName(19), "Matching Code", "[INT] ExportInkjetFile: col 19 = Matching Code" + AssertEqual verifyCsv.ColumnName(20), "ColorFilepath", "[INT] ExportInkjetFile: col 20 = ColorFilepath" + AssertEqual verifyCsv.ColumnName(21), "ColorName", "[INT] ExportInkjetFile: col 21 = ColorName" + AssertEqual verifyCsv.NumRows, expectedRows, "[INT] ExportInkjetFile: row count matches InkjetRecords" + + Dim checkRows : checkRows = 10 + If verifyCsv.NumRows < 10 Then checkRows = verifyCsv.NumRows + Dim r + For r = 0 To checkRows - 1 + Dim ballotNum : ballotNum = verifyCsv.GetCell(r, 8) + AssertEqual (Left(ballotNum, 1) <> "0" Or ballotNum = ""), True, "[INT] ExportInkjetFile: row " & r & " Ballot Number no leading zeros" + Dim matchCode : matchCode = verifyCsv.GetCell(r, 19) + AssertEqual Left(matchCode, Len(testJCode)), testJCode, "[INT] ExportInkjetFile: row " & r & " Matching Code starts with JCode" + Next + End If + + Set verifyCsv = Nothing + End If + + oConn.Open ConnectionString + Dim sideEffectRs + Set sideEffectRs = oConn.Execute("SELECT Status, InkJetJob FROM Kit WHERE ID=" & testKitID & ";") + AssertEqual sideEffectRs("Status").Value, "Done", "[INT] ExportInkjetFile: Kit.Status = Done" + AssertEqual sideEffectRs("InkJetJob").Value, 1, "[INT] ExportInkjetFile: Kit.InkJetJob = 1" + oConn.Close + + On Error Resume Next + If fso.FolderExists(integrationExportDir) Then fso.DeleteFolder(integrationExportDir, True) + On Error GoTo 0 + End If +End If +``` + +- [x] **Task 4: Verify `Option Explicit` compliance** + - File: `Tests/TrackingDataImport_TestHarness.vbs` + - Action: Review all new variable names introduced in Tasks 1–3 and confirm each has a `Dim` declaration + - Notes: New inline `Dim`s in Task 3: `kitDiscoverRs`, `testKitID`, `countRs`, `expectedRows`, `kitRsCheck`, `testJCode`, `exportSubfolders`, `csvFound`, `csvPath`, `sf`, `csvFiles`, `f`, `verifyCsv`, `csvLoaded`, `checkRows`, `r`, `ballotNum`, `matchCode`, `sideEffectRs` — all must be present + +### Acceptance Criteria + +- [ ] **AC 1:** Given Chilkat is available and `Data/webdata - Copy.mdb` contains a Kit with InkjetRecords, Jurisdiction, and Contact, when the harness calls `ExportInkjetFile(testKitID)`, then a CSV file is created under `Tests/export-test-output/-/`, it has exactly 22 columns with correct header names at indices 0/5/8/11/19/20/21, row count matches the InkjetRecords count, the first 10 rows have no leading zeros in "Ballot Number", the first 10 rows have "Matching Code" starting with JCode, and Kit.Status='Done' + Kit.InkJetJob=1 in the MDB. + +- [ ] **AC 2:** Given Chilkat is unavailable, when the harness reaches the ExportInkjetFile block, then it prints `SKIP: ExportInkjetFile integration test (Chilkat unavailable)` and no test failures are recorded. + +- [ ] **AC 3:** Given `Data/webdata - Copy.mdb` is absent or neither ACE nor Jet ADODB driver is available, when the ADODB probe block runs during harness init, then `integrationDbAvailable` is False, the test block prints a SKIP message with the reason, and no test failures are recorded. + +- [ ] **AC 4:** Given the MDB contains no Kit with all required related records (InkjetRecords + Jurisdiction + Contacts), when the discovery JOIN query runs, then it prints `SKIP: ExportInkjetFile — no complete Kit+InkjetRecords+Jurisdiction+Contact found in fixture MDB` and no test failures are recorded. + +- [ ] **AC 5:** Given a prior test run left `Tests/export-test-output/` behind (simulating a failed cleanup), when the integration test runs, then the stale folder is deleted before `ExportInkjetFile` is called and the test proceeds without contaminated assertions. + +--- + +## Additional Context + +### Dependencies + +- `Chilkat_9_5_0.Csv` COM — must be registered and unlocked (guarded by `chilkatAvailable`) +- `Microsoft.ACE.OLEDB.12.0` or `Microsoft.Jet.OLEDB.4.0` — must be available for ADODB to open the MDB +- `Data/webdata - Copy.mdb` — must exist with real Kit + InkjetRecords + KitLabels + Colors + Jurisdiction + Contacts + Settings(ElectionDate) +- `ADODB.Connection` — available on any Windows machine with Office/Access drivers + +### Testing Strategy + +Integration test only — no unit-level testing is viable for this function without significant refactoring. The test confirms the full output pipeline from DB read → CSV write → DB update. + +### Notes + +- The `ExportInkjetFile` function uses `objFSO` (not `fso`). The line `Set objFSO = fso` was added in a prior workflow pass and is already present in the harness. +- Column index 11 has a double-space in its name: `"Combined Pct_Ballot Num"` — this is as-written in the source (line 296). +- The Chilkat CSV column indices: source sets cols 0–21 (22 columns total). The `ColumnName(n)` method on the read-back CSV object uses 0-based index. +- **ADODB driver probe**: Init block tries ACE first (`Microsoft.ACE.OLEDB.12.0`), falls back to Jet (`Microsoft.Jet.OLEDB.4.0`). Bitness matters — 32-bit cscript requires 32-bit drivers. Sets `integrationDbAvailable = True/False` and `integrationDbSkipReason`. +- **oConn lifecycle**: The probe block opens and immediately closes `oConn` to validate the connection string. `ExportInkjetFile` internally calls `oConn.Open(ConnectionString)` if State=0 — so close it before calling the function. Reopen after for the side-effect assertion. +- **KitID discovery JOIN**: Uses 4-table JOIN (InkjetRecords + Kit + Jurisdiction + Contacts) to guarantee no orphan-crash inside `ExportInkjetFile` when it accesses `JurisdictionRs("Name")` or `ContactRs("Title")`. +- **Pre-call cleanup**: `integrationExportDir` is deleted before calling `ExportInkjetFile` (not just after) to prevent stale CSVs from prior failed runs contaminating assertions. diff --git a/_bmad-output/test-artifacts/automation-summary.md b/_bmad-output/test-artifacts/automation-summary.md new file mode 100644 index 0000000..85801fa --- /dev/null +++ b/_bmad-output/test-artifacts/automation-summary.md @@ -0,0 +1,187 @@ +--- +stepsCompleted: + - step-01-preflight-and-context + - step-02-identify-targets + - step-03-generate-tests + - step-03c-aggregate + - step-04-validate-and-summarize +lastStep: step-04-validate-and-summarize +lastSaved: 2026-03-17 +workflowType: testarch-automate +target: ImportService/TrackingDataImport.vbs +--- + +# Test Automation Expansion: TrackingDataImport.vbs + +## Step 1: Preflight & Context + +### Stack Detection + +- `test_stack_type`: auto → **detected: `backend`** +- No frontend tooling (no `package.json`, no Playwright/Cypress config) +- No conventional backend manifests — project is **VBScript / Classic ASP** +- Framework for this component: **standalone VBScript test harness** (`Tests/TrackingDataImport_TestHarness.vbs`) run via `cscript` + +### Execution Mode + +**Standalone** — source code only; no story/PRD/test-design artifacts exist for this component. + +### TEA Config Flags + +- `tea_use_playwright_utils`: true → **Not applicable** (no browser/HTTP layer) +- `tea_use_pactjs_utils`: true → **Not applicable** (no service contracts) +- `tea_pact_mcp`: mcp → **Not applicable** +- `tea_browser_automation`: auto → **Not applicable** (script only) + +### Existing Test Harness Summary + +`Tests/TrackingDataImport_TestHarness.vbs` uses `LoadFunctions` + `ExecuteGlobal` to extract individual functions from the source and test them in isolation. Current state: + +| Function | Status | Test Count | +|---|---|---| +| `Truncate` | ✅ Tested | 2 | +| `PadLeft` | ✅ Tested | 2 | +| `PadString` | ✅ Tested | 3 (including Null input) | +| `CleanNull` | ✅ Tested | 2 | +| `CompressArray` | ✅ Tested | 1 | +| `TrimLeadingZeros` | ✅ Tested | 2 | +| `PushNonEmptyToBottom` | ✅ Tested | 1 | +| `GetState` | ✅ Tested | 2 | +| `GetCityFromLine` | ✅ Tested | 3 (including Null) | +| `Assign` | ⚠️ Loaded, not tested | 0 | +| `Choice` | ⚠️ Loaded, not tested | 0 | +| `CheckForFiles` | ⚠️ Smoke only (empty dir) | 1 | +| `CheckStringDoesNotHaveForiegnCountries` | ❌ Not in harness | 0 | + +### Functions Out of Scope (Require COM/DB/Shell) + +`ValidJcode`, `GetSetting`, `CheckStatusFor`, `CheckForJobsToCass`, `ValidImportCSV`, `ConvertCsvToString`, `SetupKit`, `ImportCass`, `ExportMMCsv`, `RunMailManager`, `CreateExportForSnailWorks`, `CreateProofForJurisdiction`, `createTrackingInfoForKit`, `ExportInkjetFile`, `ThereAreCustomOfficeCopyJobsReady`, `CreateCustomOfficeCopyJobsInKjetFiles`, `CreateCustomOfficeCopyJobsProofFiles`, `CheckSnailWorksPurpleEnvelopeExport`, `CheckSnailWorksTrakingKitExport`, `Main`, `InitConfig`, `ProcessStatus` + +--- + +## Step 2: Coverage Plan + +### ⚠️ Highest Actual Risk — Zero Coverage (Orchestration Path) + +The import pipeline `CheckForFiles` → `ConvertCsvToString` → `ValidImportCSV` → `SetupKit` has **no automated coverage at any level**. This is the path that processes real CSV files into the database. A failure here causes silent data loss or incorrect kit creation. The unit tests below are baseline documentation; they do not protect this pipeline. + +### Targets by Test Level + +**Unit (VBScript `cscript` harness) — only applicable level for this component** + +#### P0 — Critical: Import gate + behavior-documentation + +| Target | Gap | Test Cases | Note | +|---|---|---|---| +| `ValidImportCSV` | Import gate — accepts/rejects all incoming files; Chilkat COM likely available in harness | 20-column CSV → True, 19-column → False, 0-column → False | Investigate: harness can `CreateObject("Chilkat_9_5_0.Csv")` directly | +| `CheckStringDoesNotHaveForiegnCountries` | Not in harness; pure logic, no COM dependency | clean US address → True, `"CANADA"` → False, `"JAPAN"` → False, lowercase `"canada"` → True (documents case-sensitive behavior) | Substring test `"123 Norway Ave"` → True documents assumption that input arrives pre-uppercased | +| `GetState` | `IgnoreCase=False` — mixed-case CSV input silently returns no state | `"lansing, mi 48906"` → `""` (documents real behavior risk) | Affects downstream CASS processing | + +#### P0 — Failure mode cases (surfaced by FMA) + +| Target | Case | Expected | Risk Note | +|---|---|---|---| +| `CheckStringDoesNotHaveForiegnCountries` | Null input | type mismatch (documents crash) | `InStr(Null,x)` raises runtime error | +| `GetState` | Null input | type mismatch | `RegExp.Execute(Null)` raises type mismatch | +| `Choice` | Null condition | False branch taken silently | VBScript evaluates `If Null Then` as False | + +#### P1 — Important: Edge cases for already-tested functions + +| Target | Missing Edge Cases | +|---|---| +| `TrimLeadingZeros` | `""` → `""`, `"abc"` → `"abc"`, `"0"` → `""`, `" 007"` (leading space) → `" 007"`, Null → type mismatch | +| `PadLeft` | exact-length `"007"` → `"007"`, empty string → `"000"`, Null → type mismatch | +| `PadString` | longer-than-size `"abcde"` → `"abcde"`, `""` → `" "` | +| `CompressArray` | all-empty array, all-non-empty array, single-element array | +| `GetState` | `""` → `""` | + +#### P2 — Low priority (loaded but trivially low-risk) + +| Target | Test Cases | Note | +|---|---|---| +| `Assign` | scalar string, scalar number, empty string, zero | 5-line utility; tests are baseline documentation only | +| `Choice` | True branch, False branch, computed True/False | Calls `Assign`; tests document expected delegation | +| `CheckForFiles` deeper | Requires Chilkat CSV COM for valid-file path | Skip unless `ValidImportCSV` Chilkat investigation succeeds | + +### Justification + +`ValidImportCSV` is the import gate — every CSV import decision flows through it. Its column-count check (`NumColumns = 20`) is testable if `Chilkat_9_5_0.Csv` can be instantiated in the harness via `CreateObject`. `CheckStringDoesNotHaveForiegnCountries` and `GetState` document real behavior risks around case sensitivity and substring matching in production CSV data. `Assign`/`Choice` are downgraded to P2 — baseline documentation, not safety-critical coverage. + +--- + +## Step 3: Generated Tests + +### File Modified + +`Tests/TrackingDataImport_TestHarness.vbs` — updated in-place (VBScript harness pattern, no new files required) + +### Changes Applied + +**New globals added:** +- `Dim objCSV` — makes Chilkat CSV available to `ValidImportCSV` +- `Set objFSO = fso` — ensures `objFSO` is set early for loaded functions +- `Dim chilkatAvailable` + COM probe block — skips `ValidImportCSV` tests gracefully if Chilkat not registered + +**New entries in `functionNames` array:** +- `"CheckStringDoesNotHaveForiegnCountries"` +- `"ValidImportCSV"` + +**New test assertions (38 new tests):** + +| Priority | Function | Cases | +|---|---|---| +| P0 | `CheckStringDoesNotHaveForiegnCountries` | 7 (clean US, CANADA, JAPAN, Norway Ave, lowercase canada, empty, Null) | +| P0 | `GetState` | 2 (lowercase miss, empty) | +| P0 | `Choice` | 1 (Null condition) | +| P0 | `ValidImportCSV` | 3 (20-col accept, 19-col reject, empty reject) — conditional on Chilkat | +| P1 | `TrimLeadingZeros` | 4 (empty, non-numeric, single zero, leading space) | +| P1 | `PadLeft` | 2 (exact length, empty string) | +| P1 | `PadString` | 2 (longer-than-size, empty string) | +| P1 | `CompressArray` | 3 (all-empty, all-non-empty, single-element) | +| P2 | `Assign` | 4 (string, number, empty, zero) | +| P2 | `Choice` | 4 (True, False, computed True, computed False) | + +**Total new tests: 32** (+ 3 conditional on Chilkat availability = 35 max) + +### Test Count Summary + +| | Count | +|---|---| +| Existing tests (before this pass) | 19 | +| New tests added | 32 (+3 conditional) | +| **Total (Chilkat available)** | **54** | +| **Total (Chilkat unavailable)** | **51** | + +## Validation + +### Checklist Result: ✅ PASS + +All applicable items passed. Full checklist adapted for VBScript `cscript` harness (Playwright/TypeScript items marked N/A). + +**Key verifications:** +- `Choice(Null, "yes", "no")` → `"no"` confirmed: VBScript `If Null Then` evaluates False +- `CheckStringDoesNotHaveForiegnCountries(Null)` → `True` confirmed: `InStr(Null, x)` returns Null (falsy in If) +- `ValidImportCSV` guard pattern correct: `chilkatAvailable` flag skips gracefully if COM absent +- All `Dim` declarations satisfy `Option Explicit` requirement +- No shared mutable state between test groups + +### Assumptions and Risks + +- `ValidImportCSV` tests depend on `Chilkat_9_5_0.Csv` COM registration on the machine running the harness. The guard skips them cleanly if absent. +- The orchestration path (`CheckForFiles` → `ConvertCsvToString` → `ValidImportCSV` → `SetupKit`) remains **without automated coverage** — this is the highest actual risk. Database-backed integration tests would require a seeded MDB fixture and are out of scope for the cscript harness pattern. +- `CheckStringDoesNotHaveForiegnCountries` substring and case-sensitivity tests are **behavior-documentation tests**, not defect tests. They document the assumption that upstream CSV data arrives pre-uppercased. +- `GetState` lowercase test documents a known production risk: mixed-case city/state lines in real CSV data silently return no state. + +### How to Run + +``` +cscript Tests\TrackingDataImport_TestHarness.vbs +``` + +Expected output (Chilkat available): `Passed: 54, Failed: 0` +Expected output (Chilkat unavailable): `SKIP: ValidImportCSV...` then `Passed: 51, Failed: 0` + +### Recommended Next Workflow + +- `/bmad-tea-bmad-testarch-test-review` — review coverage quality and identify remaining gaps +- Optional follow-up: add seeded MDB integration tests for the `CheckForFiles` → `SetupKit` pipeline using a disposable MDB copy (highest actual risk, currently uncovered) diff --git a/_bmad-output/test-artifacts/test-design-architecture.md b/_bmad-output/test-artifacts/test-design-architecture.md new file mode 100644 index 0000000..d90f824 --- /dev/null +++ b/_bmad-output/test-artifacts/test-design-architecture.md @@ -0,0 +1,248 @@ +--- +stepsCompleted: + - step-01-detect-mode + - step-02-load-context + - step-03-risk-and-testability + - step-04-coverage-plan + - step-05-generate-output +lastStep: step-05-generate-output +lastSaved: 2026-03-17 +workflowType: testarch-test-design +inputDocuments: + - D:\Development\Tracking_Kits\docs\project-overview.md + - D:\Development\Tracking_Kits\docs\architecture.md + - D:\Development\Tracking_Kits\docs\module-map.md + - D:\Development\Tracking_Kits\docs\development-guide.md + - D:\Development\Tracking_Kits\_bmad-output\project-context.md + - D:\Development\Tracking_Kits\App\Controllers\Kit\KitController.asp + - D:\Development\Tracking_Kits\App\DomainModels\InkjetRecordsRepository.asp + - D:\Development\Tracking_Kits\App\Views\Kit\SwitchBoardPurpleEnvelopeEdit.asp +--- + +# Test Design for Architecture: Purple Envelope Edit and Ballot Range Report + +**Purpose:** Architectural concerns, testability gaps, and NFR requirements for review by Dev/Architecture before adding regression tests for the current purple-envelope workflow. + +**Date:** 2026-03-17 +**Author:** Daniel Covington +**Status:** Architecture Review Pending +**Project:** workspace +**PRD Reference:** Brownfield functional references: `docs/project-overview.md`, `docs/module-map.md`, and the currently deployed purple-envelope behavior +**ADR Reference:** Brownfield architecture references: `docs/architecture.md` and `App/Controllers/Kit/KitController.asp` + +--- + +## Executive Summary + +**Scope:** Preserve current behavior for `SwitchBoardPurpleEnvelopeEdit`, including report rendering, mixed-format precinct ordering, election-date formatting, color-assignment forms, and print-only report scoping. + +**Business Context**: + +- **Revenue/Impact:** Operational election-mail output; wrong report data can cause incorrect ballot packaging and operator rework. +- **Problem:** The feature is currently exercised mostly through manual IIS verification, so small controller/view/repository regressions can silently change report output. +- **GA Launch:** Immediate brownfield regression protection for current production behavior. + +**Architecture**: + +- **Key Decision 1:** Purple-envelope behavior remains in Classic ASP controller/view/repository layers, not a separate service. +- **Key Decision 2:** Report content is rendered as HTML from `KitController` + `InkjetRecordsRepository` + `SwitchBoardPurpleEnvelopeEdit.asp`. +- **Key Decision 3:** Mixed-format precinct ordering is handled in VBScript controller helpers, while ballot range aggregation is still SQL-backed. + +**Expected Scale**: + +- Single-kit page render with precinct rows sourced from `InkjetRecords` +- Operator-facing print workflow from browser print preview +- Existing automated test harness limited to ASPUnit helper-style tests + +**Risk Summary:** + +- **Total risks:** 7 +- **High-priority (>=6):** 3 risks requiring mitigation before relying on automated regression coverage +- **Test effort:** ~14-19 targeted automated checks plus 2 manual print checks (~1-1.5 QA weeks) + +--- + +## Quick Guide + +### BLOCKERS - Team Must Decide + +1. **R-006: No isolated fixture path for Kit/Inkjet/Settings data** - Tests need a disposable MDB or deterministic seed/reset strategy before meaningful controller/repository regression coverage can be added. Recommended owner: Dev. +2. **R-002: Ballot range correctness is data-sensitive** - We need agreed fixture data covering numeric, zero-padded, and alpha-suffixed precincts plus min/max ballot scenarios. Recommended owner: Dev + QA. +3. **R-001: Mixed-format precinct ordering is custom logic** - The current order rules must be frozen as explicit expected outputs before tests are written. Recommended owner: Product/Dev/QA. + +### HIGH PRIORITY - Team Should Validate + +1. **R-003: Election date formatting/fallback behavior** - Approve the rule set: valid dates render `Mon-YYYY`, invalid or missing values fall back without crashing. +2. **R-004: Print-only report scope depends on browser print behavior** - Approve a split strategy: automated HTML/CSS assertions plus manual browser print smoke for final confidence. +3. **R-005: Color-assignment regressions can hide behind the same screen** - Validate that the report tests must also preserve existing `AssignKitColorPost` and `AssignPrecinctColorsPost` behavior. + +### INFO ONLY - Solutions Provided + +1. **Test strategy:** favor repository/controller integration checks plus limited manual IIS print verification. +2. **Tooling:** reuse ASPUnit, a disposable test MDB copy, and HTML response assertions. +3. **Coverage:** focus first on sorting, aggregation, date formatting, same-screen regression, and print scoping. +4. **Gate guidance:** P0 automated checks must pass 100%; P1 >=95%; manual IIS print smoke required before release for print-impacting changes. + +--- + +## For Architects and Devs - Open Topics + +### Risk Assessment + +**Total risks identified:** 7 (3 high-priority score >=6, 4 medium, 0 low) + +#### High-Priority Risks (Score >=6) - Immediate Attention + +| Risk ID | Category | Description | Probability | Impact | Score | Mitigation | Owner | Timeline | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **R-001** | **DATA** | Mixed-format precinct sort order changes (`0003`, `12A`, alpha-only values) and produces wrong operator-facing sequence | 2 | 3 | **6** | Lock expected order with seeded regression fixtures and HTML assertions | Dev + QA | Before next release | +| **R-002** | **DATA** | Ballot range query/regression returns wrong low or high ballot numbers per precinct | 2 | 3 | **6** | Add repository-level aggregation checks against known datasets | Dev + QA | Before next release | +| **R-006** | **TECH** | No isolated test-data harness for `Kit`, `InkjetRecords`, `Settings`, and `Colors` blocks stable regression coverage | 3 | 2 | **6** | Create disposable MDB copy and reset/seeding conventions for tests | Dev | Before automated suite work | + +#### Medium-Priority Risks (Score 3-5) + +| Risk ID | Category | Description | Probability | Impact | Score | Mitigation | Owner | +| --- | --- | --- | --- | --- | --- | --- | --- | +| R-003 | BUS | Election date header shows wrong format or blank value unexpectedly | 2 | 2 | 4 | Add valid, invalid, and missing-setting render checks | QA | +| R-004 | OPS | Print-only output regresses and shows extra page content or loses repeated-page spacing | 2 | 2 | 4 | Add HTML/CSS assertions and manual print smoke | QA | +| R-005 | BUS | Same-screen changes break existing color-assignment workflows while report tests are added | 2 | 2 | 4 | Add regression checks for both POST actions | Dev + QA | +| R-007 | BUS | Empty precinct data renders broken or confusing output instead of the current no-data row | 2 | 2 | 4 | Add explicit empty-state render test | QA | + +#### Risk Category Legend + +- **TECH**: architectural or testability risk +- **DATA**: incorrect ballot or precinct data interpretation +- **BUS**: operator-facing workflow correctness +- **OPS**: environment or print/runtime behavior + +--- + +### Testability Concerns and Architectural Gaps + +#### 1. Blockers to Fast Feedback + +| Concern | Impact | What Architecture Must Provide | Owner | Timeline | +| --- | --- | --- | --- | --- | +| **No disposable test database pattern** | Automated tests will share mutable data and become brittle | A documented temp MDB copy/reset approach for ASPUnit-backed integration tests | Dev | Pre-implementation | +| **No render-test seam for controller output** | Report assertions stay manual and expensive | A small harness or convention for invoking `SwitchBoardPurpleEnvelopeEdit` against seeded data and asserting HTML | Dev | Pre-implementation | +| **Implicit behavior contract only lives in code/user feedback** | Tests may encode the wrong business rule | A short frozen rules list for sorting, header formatting, and empty-state expectations | Dev + QA + Stakeholders | Pre-implementation | + +#### 2. Architectural Improvements Needed + +1. **Stabilize feature fixtures** + - **Current problem:** no reusable seed set for mixed precinct and ballot-range cases. + - **Required change:** create named fixture datasets for numeric-only, zero-padded, alpha-suffixed, alpha-only, and empty-state kits. + - **Impact if not fixed:** tests will either miss key edge cases or rely on hand-built ad hoc data. + - **Owner:** Dev + - **Timeline:** before automated test implementation + +2. **Separate deterministic logic from page orchestration where practical** + - **Current problem:** ordering/date helpers are private controller methods, making low-level regression checks harder. + - **Required change:** either expose deterministic helper seams or cover the helpers through stable page-render assertions. + - **Impact if not fixed:** coverage will skew toward broad render tests only. + - **Owner:** Dev + - **Timeline:** implementation phase if needed + +3. **Document browser-dependent print boundaries** + - **Current problem:** browser headers/footers and repeated-page spacing are only partially controllable in app code. + - **Required change:** record which print expectations are automatable versus manual. + - **Impact if not fixed:** release disputes on “test passed but print preview changed”. + - **Owner:** QA + - **Timeline:** before sign-off + +--- + +### Testability Assessment Summary + +#### What Works Well + +- Existing repository boundaries make ballot-range aggregation testable without full browser automation. +- The report HTML is deterministic from seeded data, which is good for response-body assertions. +- The feature is localized to `KitController`, `InkjetRecordsRepository`, `KitViewModels`, and one view template. +- ASPUnit already exists, so the project has a place to add targeted regression checks. + +#### Accepted Trade-offs + +- Browser-generated print headers/footers will remain manual-verification territory. +- Full visual print fidelity on Windows/IIS is accepted as a release smoke check, not a unit-level assertion target. + +--- + +### Risk Mitigation Plans (High-Priority Risks >=6) + +#### R-001: Mixed-format precinct sort regression (Score: 6) - HIGH + +**Mitigation Strategy:** + +1. Define the expected canonical sort order for representative precinct values. +2. Seed one kit containing `0001`, `0003`, `12`, `12A`, `12B`, and alpha-only precincts. +3. Assert that both the color table and report table render in the same expected order. + +**Owner:** Dev + QA +**Timeline:** Before next release +**Status:** Planned +**Verification:** Automated HTML assertions against the seeded render output + +#### R-002: Ballot range aggregation regression (Score: 6) - HIGH + +**Mitigation Strategy:** + +1. Seed multiple `InkjetRecords` rows per precinct with known min/max ballot numbers. +2. Assert repository output for each precinct’s low/high values. +3. Assert the rendered HTML shows the same values and no cross-precinct bleed. + +**Owner:** Dev + QA +**Timeline:** Before next release +**Status:** Planned +**Verification:** Repository integration checks plus page-render verification + +#### R-006: Missing isolated test-data harness (Score: 6) - HIGH + +**Mitigation Strategy:** + +1. Stand up a disposable test MDB copy for ASPUnit/controller integration runs. +2. Create reset hooks for `Kit`, `InkjetRecords`, `Settings`, and `Colors`. +3. Document fixture naming and cleanup rules for parallel-safe future tests. + +**Owner:** Dev +**Timeline:** Before automated suite expansion +**Status:** Planned +**Verification:** Test runs can seed, execute, and reset without polluting shared data + +--- + +### Assumptions and Dependencies + +#### Assumptions + +1. Brownfield docs plus current user-approved behavior are sufficient substitutes for a formal PRD for this feature slice. +2. IIS-backed manual verification remains available for final print checks. +3. Existing status strings and form routing remain part of the contract and should not be normalized during test work. + +#### Dependencies + +1. Disposable test data store available before controller/repository automation begins. +2. Mixed-format precinct fixture definitions agreed before expected-order assertions are written. +3. QA has access to a Windows/IIS browser for manual print smoke validation. + +#### Risks to Plan + +- **Risk:** undocumented operator expectations about sort order + - **Impact:** the suite may freeze the wrong behavior + - **Contingency:** validate the expected fixture output with stakeholders before coding assertions + +--- + +**End of Architecture Document** + +**Next Steps for Architecture/Dev Team:** + +1. Approve the sort-order and date-format behavior contract. +2. Provide a disposable MDB fixture/reset approach. +3. Confirm manual print checks remain part of release validation for this feature. + +**Next Steps for QA Team:** + +1. Use the companion QA doc for concrete scenario planning. +2. Build seeded datasets around mixed-format precincts and ballot ranges. +3. Add manual print preview checks only for browser-dependent behavior. diff --git a/_bmad-output/test-artifacts/test-design-progress.md b/_bmad-output/test-artifacts/test-design-progress.md new file mode 100644 index 0000000..9c1c084 --- /dev/null +++ b/_bmad-output/test-artifacts/test-design-progress.md @@ -0,0 +1,80 @@ +--- +stepsCompleted: + - step-01-detect-mode + - step-02-load-context + - step-03-risk-and-testability + - step-04-coverage-plan + - step-05-generate-output +lastStep: step-05-generate-output +lastSaved: 2026-03-17 +workflowType: testarch-test-design +inputDocuments: + - D:\Development\Tracking_Kits\docs\project-overview.md + - D:\Development\Tracking_Kits\docs\architecture.md + - D:\Development\Tracking_Kits\docs\module-map.md + - D:\Development\Tracking_Kits\docs\development-guide.md + - D:\Development\Tracking_Kits\_bmad-output\project-context.md + - D:\Development\Tracking_Kits\App\Controllers\Kit\KitController.asp + - D:\Development\Tracking_Kits\App\DomainModels\InkjetRecordsRepository.asp + - D:\Development\Tracking_Kits\App\Views\Kit\SwitchBoardPurpleEnvelopeEdit.asp + - D:\Development\Tracking_Kits\Tests\Test_All.asp + - D:\Development\Tracking_Kits\_bmad\tea\testarch\knowledge\adr-quality-readiness-checklist.md + - D:\Development\Tracking_Kits\_bmad\tea\testarch\knowledge\risk-governance.md + - D:\Development\Tracking_Kits\_bmad\tea\testarch\knowledge\probability-impact.md + - D:\Development\Tracking_Kits\_bmad\tea\testarch\knowledge\test-levels-framework.md + - D:\Development\Tracking_Kits\_bmad\tea\testarch\knowledge\test-priorities-matrix.md + - D:\Development\Tracking_Kits\_bmad\tea\testarch\knowledge\test-quality.md +--- + +# Test Design Progress + +## Step 1 - Detect Mode + +- Selected **system-level mode** +- Reason: user requested a risk-based plan for current functionality, no sprint-status artifact exists, and brownfield architecture/docs were available +- Constraint noted: no formal PRD/ADR exists for this feature slice, so brownfield docs and current approved behavior were used as the functional baseline + +## Step 2 - Load Context + +- Loaded project context, module docs, development/testing guidance, and the exact purple-envelope controller/repository/view files +- Loaded TEA knowledge fragments for ADR readiness, risk scoring, probability/impact, test levels, priorities, and test quality +- Confirmed current test framework is ASPUnit with helper-focused coverage, not browser E2E automation + +## Step 3 - Risk and Testability + +- Identified 7 risks total, with 3 high-priority risks: + - R-001 mixed-format precinct ordering regression + - R-002 ballot range aggregation regression + - R-006 missing isolated test-data harness +- Identified primary testability blockers: + - no disposable test DB convention + - no controller render harness + - behavior contract is mostly implicit in code and operator expectations + +## Step 4 - Coverage Plan + +- Defined a focused regression plan: + - P0 ~4 checks + - P1 ~6 checks + - P2 ~4 checks + - P3 ~2 manual print checks +- Chose integration-style ASPUnit/IIS verification as the main automated level +- Kept print-preview specifics as manual smoke for browser-controlled behavior + +## Step 5 - Generate Output + +- Wrote architecture-focused test design: + - `D:\Development\Tracking_Kits\_bmad-output\test-artifacts\test-design-architecture.md` +- Wrote QA execution plan: + - `D:\Development\Tracking_Kits\_bmad-output\test-artifacts\test-design-qa.md` +- Wrote BMAD handoff: + - `D:\Development\Tracking_Kits\_bmad-output\test-artifacts\test-design\workspace-handoff.md` + +## Completion Report + +- **Mode used:** system-level +- **Key gate thresholds:** P0 100% pass, P1 >=95%, manual Windows print smoke for print-affecting changes +- **Open assumptions:** + - brownfield docs are acceptable substitutes for a formal PRD + - a disposable MDB/reset approach will be created before automation work starts + - browser header/footer behavior remains outside reliable app-level automation diff --git a/_bmad-output/test-artifacts/test-design-qa.md b/_bmad-output/test-artifacts/test-design-qa.md new file mode 100644 index 0000000..7651c9a --- /dev/null +++ b/_bmad-output/test-artifacts/test-design-qa.md @@ -0,0 +1,311 @@ +--- +stepsCompleted: + - step-01-detect-mode + - step-02-load-context + - step-03-risk-and-testability + - step-04-coverage-plan + - step-05-generate-output +lastStep: step-05-generate-output +lastSaved: 2026-03-17 +workflowType: testarch-test-design +inputDocuments: + - D:\Development\Tracking_Kits\docs\project-overview.md + - D:\Development\Tracking_Kits\docs\architecture.md + - D:\Development\Tracking_Kits\docs\module-map.md + - D:\Development\Tracking_Kits\docs\development-guide.md + - D:\Development\Tracking_Kits\_bmad-output\project-context.md + - D:\Development\Tracking_Kits\App\Controllers\Kit\KitController.asp + - D:\Development\Tracking_Kits\App\DomainModels\InkjetRecordsRepository.asp + - D:\Development\Tracking_Kits\App\Views\Kit\SwitchBoardPurpleEnvelopeEdit.asp + - D:\Development\Tracking_Kits\Tests\Test_All.asp +--- + +# Test Design for QA: Purple Envelope Edit and Ballot Range Report + +**Purpose:** Test execution recipe for QA and devs adding regression coverage for the current purple-envelope edit/report workflow. + +**Date:** 2026-03-17 +**Author:** Daniel Covington +**Status:** Draft +**Project:** workspace + +**Related:** See `test-design-architecture.md` for architectural blockers and testability gaps. + +--- + +## Executive Summary + +**Scope:** Preserve the current behavior of the purple-envelope edit screen, including report content, mixed-format precinct ordering, election-date rendering, color assignment, empty states, and print-only markup boundaries. + +**Risk Summary:** + +- Total Risks: 7 (3 high-priority, 4 medium) +- Critical Categories: DATA, TECH, BUS + +**Coverage Summary:** + +- P0 tests: ~4 +- P1 tests: ~6 +- P2 tests: ~4 +- P3 tests: ~2 manual checks +- **Total:** ~16 targeted checks plus 2 manual browser-print checks (~1-1.5 QA weeks) + +--- + +## Not in Scope + +| Item | Reasoning | Mitigation | +| --- | --- | --- | +| **ReportMan/PDF exports** | Not part of this feature slice; separate export path and COM dependencies | Covered by existing manual export verification | +| **Browser-generated print headers/footers** | Controlled by browser settings, not reliably by page code | Manual print smoke checklist documents expected operator setup | +| **Jurisdiction import or unrelated kit workflows** | Outside the current purple-envelope regression target | Existing module-level validation remains separate | + +--- + +## Dependencies & Test Blockers + +### Backend/Architecture Dependencies (Pre-Implementation) + +1. **Disposable test MDB strategy** - Dev - Pre-implementation + - QA needs a resettable database copy or equivalent seed/reset routine. + - Without this, controller/repository tests will be brittle and non-repeatable. + +2. **Seed dataset for mixed-format precincts** - Dev + QA - Pre-implementation + - QA needs representative data for `000x`, numeric, alpha-suffixed, alpha-only, and empty-state precinct cases. + - Without this, ordering and aggregation checks will not cover the risky paths. + +3. **Controller render harness convention** - Dev - Pre-implementation + - QA needs a repeatable way to exercise `SwitchBoardPurpleEnvelopeEdit` and inspect the HTML body under IIS/ASPUnit-compatible flow. + - Without this, only repository tests will be automated and the view contract stays manual. + +### QA Infrastructure Setup (Pre-Implementation) + +1. **ASPUnit extension points** + - Add one focused test container for purple-envelope regression checks. + - Keep tests deterministic and data-seeded. + +2. **Test data fixtures** + - Fixture set A: ballot ranges with known low/high numbers. + - Fixture set B: mixed-format precinct ordering. + - Fixture set C: missing/invalid `Electiondate`. + +3. **Windows/IIS smoke environment** + - One browser-print smoke pass for report-only printing and repeated-page top spacing. + +--- + +## Risk Assessment + +### High-Priority Risks (Score >=6) + +| Risk ID | Category | Description | Score | QA Test Coverage | +| --- | --- | --- | --- | --- | +| **R-001** | DATA | Mixed-format precinct ordering changes | **6** | Seeded render assertions for both color table and report table | +| **R-002** | DATA | Wrong low/high ballot aggregation per precinct | **6** | Repository aggregation tests plus rendered-value checks | +| **R-006** | TECH | No isolated fixture/reset path for stable tests | **6** | Test harness readiness criteria and pre-test reset validation | + +### Medium/Low-Priority Risks + +| Risk ID | Category | Description | Score | QA Test Coverage | +| --- | --- | --- | --- | --- | +| R-003 | BUS | Election date renders incorrectly or crashes on bad input | 4 | Valid/missing/invalid setting render checks | +| R-004 | OPS | Print-only scope or top spacing regresses | 4 | HTML/CSS assertions plus manual print smoke | +| R-005 | BUS | Color-assignment actions regress while report tests are added | 4 | POST regression checks for kit-wide and precinct-specific updates | +| R-007 | BUS | Empty precinct state breaks current report contract | 4 | Explicit no-data render check | + +--- + +## Entry Criteria + +- [ ] Disposable test MDB or equivalent reset strategy available +- [ ] Mixed-format precinct fixtures agreed and documented +- [ ] IIS-accessible test path for controller/render verification available +- [ ] Existing `Tests/Test_All.asp` runner still executes successfully +- [ ] Manual Windows browser available for final print smoke + +## Exit Criteria + +- [ ] All P0 checks passing +- [ ] All P1 checks passing or formally triaged +- [ ] No open high-risk regression on ordering, aggregation, or data header behavior +- [ ] HTML render contract validated for empty state and report-only print container +- [ ] Manual print smoke completed for report-only output and repeated-page spacing + +--- + +## Test Coverage Plan + +**Note:** P0/P1/P2/P3 represent priority and risk, not execution timing. + +### P0 (Critical) + +**Criteria:** Preserves operator-facing data correctness and high-risk regression paths with no acceptable workaround. + +| Test ID | Requirement | Test Level | Risk Link | Notes | +| --- | --- | --- | --- | --- | +| **P0-001** | Report shows correct jurisdiction/JCode and formatted election label for seeded valid data | Integration | R-003 | Assert rendered HTML content | +| **P0-002** | Mixed-format precincts render in the approved ascending order | Integration | R-001 | Cover `000x`, numeric, alpha-suffixed, alpha-only | +| **P0-003** | Each precinct shows correct low/high ballot numbers from seeded records | Integration | R-002 | Assert repository output and rendered HTML | +| **P0-004** | Empty precinct dataset renders current no-data message instead of broken markup | Integration | R-007 | Protect empty-state contract | + +**Total P0:** ~4 tests + +--- + +### P1 (High) + +**Criteria:** Preserves important same-screen workflows and print-related regressions with moderate-to-high operational impact. + +| Test ID | Requirement | Test Level | Risk Link | Notes | +| --- | --- | --- | --- | --- | +| **P1-001** | `AssignKitColorPost` updates all `InkjetRecords.ColorId` rows for the kit | Integration | R-005 | Seeded DB verification | +| **P1-002** | `AssignPrecinctColorsPost` updates only the targeted precinct rows | Integration | R-005 | Ensure no cross-precinct bleed | +| **P1-003** | Missing `Electiondate` does not error and leaves report date blank | Integration | R-003 | Assert safe fallback | +| **P1-004** | Invalid `Electiondate` value preserves non-crashing fallback behavior | Integration | R-003 | Use non-date string fixture | +| **P1-005** | Print container isolates the report section in markup/CSS | Integration | R-004 | Assert presence of `#purple-envelope-report-print` rules | +| **P1-006** | `Ready To Assign STIDS` status still gates the existing form block | Integration | R-005 | Regression for same-screen conditional behavior | + +**Total P1:** ~6 tests + +--- + +### P2 (Medium) + +**Criteria:** Lower-risk formatting and regression checks that still help freeze today’s behavior. + +| Test ID | Requirement | Test Level | Risk Link | Notes | +| --- | --- | --- | --- | --- | +| **P2-001** | Full-year format renders as `Mon-YYYY` for valid election dates | Integration | R-003 | Example `May-2026` | +| **P2-002** | Print spacer row and top buffer CSS remain present | Integration | R-004 | Markup/CSS contract only | +| **P2-003** | Report font-size contract remains at current reduced print size | Integration | R-004 | CSS assertion | +| **P2-004** | Color table and report table stay aligned on precinct order | Integration | R-001 | Prevent divergent ordering paths | + +**Total P2:** ~4 tests + +--- + +### P3 (Low) + +**Criteria:** Manual, browser-dependent, or release-smoke-only validation. + +| Test ID | Requirement | Test Level | Notes | +| --- | --- | --- | --- | +| **P3-001** | Browser print preview shows only the report section | Manual Browser | Requires operator print settings | +| **P3-002** | Page 2+ maintains visible top breathing room in print preview | Manual Browser | Validate on Windows/IIS browser | + +**Total P3:** ~2 checks + +--- + +## Execution Strategy + +### Every PR: ASPUnit + seeded render/repository checks (~5-10 min) + +- Run the existing `Tests/Test_All.asp` suite +- Run the new purple-envelope regression container against disposable seeded data +- Keep automated checks focused on repository correctness and rendered HTML contract + +### Nightly: expanded seeded regression pass (~10-20 min) + +- Re-run the purple-envelope suite against larger mixed-format datasets +- Include any fixture-reset verification and broader same-screen regression checks + +### Weekly or Pre-Release: manual Windows/IIS print smoke (~15-30 min) + +- Confirm report-only printing +- Confirm repeated-page top spacing +- Confirm browser-dependent behavior still matches operator expectations + +--- + +## QA Effort Estimate + +| Priority | Count | Effort Range | Notes | +| --- | --- | --- | --- | +| P0 | ~4 | ~8-12 hours | Core seeded render/aggregation checks | +| P1 | ~6 | ~10-16 hours | POST regressions, fallbacks, print markup | +| P2 | ~4 | ~6-10 hours | Formatting and alignment checks | +| P3 | ~2 | ~2-4 hours | Manual print verification | +| **Total** | ~16 | **~26-42 hours** | **~1-1.5 QA weeks** | + +**Assumptions:** + +- Disposable fixture/reset path is provided early. +- Tests reuse existing ASPUnit conventions instead of introducing a new framework. +- Manual print validation remains intentionally small and release-focused. + +--- + +## Implementation Planning Handoff + +| Work Item | Owner | Target Milestone (Optional) | Dependencies/Notes | +| --- | --- | --- | --- | +| Add disposable MDB copy/reset process for purple-envelope tests | Dev | Sprint next | Required before automation | +| Add repository regression tests for ballot range aggregation | Dev/QA | Sprint next | Uses fixture set A | +| Add controller/render assertions for report HTML and sort order | Dev/QA | Sprint next | Uses fixture sets A/B/C | +| Add manual print smoke checklist to release flow | QA | Pre-release | Browser-dependent only | + +--- + +## Tooling & Access + +| Tool or Service | Purpose | Access Required | Status | +| --- | --- | --- | --- | +| IIS-hosted local app | Execute controller/render tests | Windows + IIS site access | Ready | +| Disposable MDB copy | Stable seeded data | File-system access to test DB copy | Pending | +| ASPUnit runner | Automated regression execution | Existing `Tests/Test_All.asp` access | Ready | +| Windows browser print preview | Manual print smoke | Local browser access | Ready | + +**Access requests needed (if any):** + +- [ ] Disposable test database path and reset ownership confirmed + +--- + +## Interworking & Regression + +| Service/Component | Impact | Regression Scope | Validation Steps | +| --- | --- | --- | --- | +| **KitController** | Orchestrates page model and POST actions | Existing edit page behavior plus report additions | Render and POST regression checks | +| **InkjetRecordsRepository** | Supplies precinct lists, aggregation, and color updates | Ordering-sensitive and data-sensitive queries | Seeded repository assertions | +| **SettingsRepository** | Supplies `Electiondate` | Header formatting/fallback | Valid/missing/invalid setting cases | +| **SwitchBoardPurpleEnvelopeEdit.asp** | Final HTML/print contract | Report-only container, table content, empty state | HTML response assertions + manual print smoke | + +**Regression test strategy:** + +- Existing `Tests/Test_All.asp` must still pass. +- New seeded regression checks should run before release on any change touching the purple-envelope screen, repository, or print CSS. +- Manual Windows print smoke remains required for print-affecting changes. + +--- + +## Appendix A: Code Examples & Tagging + +For this repo, prefer ASPUnit naming aligned to the existing suite rather than Playwright tagging: + +- `PurpleEnvelopeReport_Should_Render_ElectionDate` +- `PurpleEnvelopeReport_Should_Sort_MixedPrecincts_Ascending` +- `PurpleEnvelopeReport_Should_Show_BallotRanges_PerPrecinct` +- `AssignPrecinctColors_Should_Update_Only_TargetPrecinct` + +Keep tests deterministic: + +- Seed explicit fixture data +- Assert HTML or repository outputs directly +- Avoid browser timing waits in automated checks + +--- + +## Appendix B: Knowledge Base References + +- `risk-governance.md` +- `probability-impact.md` +- `test-levels-framework.md` +- `test-priorities-matrix.md` +- `test-quality.md` + +--- + +**Generated by:** BMad TEA Agent +**Workflow:** `_bmad/tea/testarch/bmad-testarch-test-design` +**Mode:** System-level (brownfield feature scope) diff --git a/_bmad-output/test-artifacts/test-design/workspace-handoff.md b/_bmad-output/test-artifacts/test-design/workspace-handoff.md new file mode 100644 index 0000000..43669b6 --- /dev/null +++ b/_bmad-output/test-artifacts/test-design/workspace-handoff.md @@ -0,0 +1,85 @@ +--- +title: TEA Test Design -> BMAD Handoff Document +version: "1.0" +workflowType: testarch-test-design-handoff +inputDocuments: + - D:\Development\Tracking_Kits\_bmad-output\test-artifacts\test-design-architecture.md + - D:\Development\Tracking_Kits\_bmad-output\test-artifacts\test-design-qa.md +sourceWorkflow: testarch-test-design +generatedBy: TEA Master Test Architect +generatedAt: "2026-03-17T00:00:00" +projectName: workspace +--- + +# TEA -> BMAD Integration Handoff + +## Purpose + +This document bridges the purple-envelope regression test design with future BMAD epic/story work so the current behavior contract is preserved during implementation. + +## TEA Artifacts Inventory + +| Artifact | Path | BMAD Integration Point | +| --- | --- | --- | +| Test Design Architecture | `D:\Development\Tracking_Kits\_bmad-output\test-artifacts\test-design-architecture.md` | Architectural blockers, risk mitigation, testability requirements | +| Test Design QA | `D:\Development\Tracking_Kits\_bmad-output\test-artifacts\test-design-qa.md` | Story acceptance criteria and regression scenario coverage | +| Risk Assessment | embedded in both docs | Epic quality gates and story priority | + +## Epic-Level Integration Guidance + +### Risk References + +- **R-001 / R-002** must be treated as epic-level quality blockers because ordering or aggregation defects change operator-facing output. +- **R-006** must be scheduled as enabling work before expecting reliable automated regression coverage. + +### Quality Gates + +- No story touching the purple-envelope edit page is complete until mixed-format precinct ordering is proven against seeded data. +- No story touching the report query/view is complete until ballot low/high aggregation is verified. +- Print-affecting changes require a manual Windows/IIS print smoke before release. + +## Story-Level Integration Guidance + +### P0/P1 Test Scenarios -> Story Acceptance Criteria + +- The purple-envelope report renders jurisdiction, JCode, and election date exactly as currently approved. +- Precincts sort in the frozen ascending order for zero-padded, numeric, and alpha-suffixed values. +- Each precinct shows the correct low/high ballot number range. +- Kit-wide and precinct-specific color updates still work on the same screen. +- Empty precinct datasets show the current no-data row without error. + +### Data-TestId Requirements + +- Not applicable for Classic ASP in the current implementation. +- Prefer stable HTML markers, predictable headings, and table structure for server-render assertions. + +## Risk-to-Story Mapping + +| Risk ID | Category | P×I | Recommended Story/Epic | Test Level | +| --- | --- | --- | --- | --- | +| R-001 | DATA | 2x3 | Purple-envelope report ordering contract | Integration | +| R-002 | DATA | 2x3 | Purple-envelope ballot aggregation contract | Integration | +| R-003 | BUS | 2x2 | Election date render/fallback behavior | Integration | +| R-004 | OPS | 2x2 | Print-only report markup and manual print smoke | Integration + Manual | +| R-005 | BUS | 2x2 | Preserve color-assignment workflows on same screen | Integration | +| R-006 | TECH | 3x2 | Disposable MDB/reset harness for regression suite | Enabler | +| R-007 | BUS | 2x2 | Empty-state render contract | Integration | + +## Recommended BMAD -> TEA Workflow Sequence + +1. TEA Test Design (`TD`) -> complete +2. BMAD Create Epics & Stories -> include regression-preservation stories and test-harness enabler work +3. TEA ATDD (`AT`) -> generate failing checks for P0 report-ordering and aggregation scenarios +4. BMAD Implementation -> add fixtures/tests/harness work +5. TEA Automate (`TA`) -> expand the suite beyond the initial P0/P1 checks +6. TEA Trace (`TR`) -> verify risk coverage completeness + +## Phase Transition Quality Gates + +| From Phase | To Phase | Gate Criteria | +| --- | --- | --- | +| Test Design | Epic/Story Creation | R-001, R-002, and R-006 mitigation work represented in planning | +| Epic/Story Creation | ATDD | P0 scenarios for ordering and aggregation translated into executable checks | +| ATDD | Implementation | Seed fixtures and disposable DB approach agreed | +| Implementation | Test Automation | Automated P0 checks passing | +| Test Automation | Release | Manual print smoke complete for print-affecting changes |