Selaa lähdekoodia

Remove Inbound Tracking option

master
Daniel Covington 1 päivä sitten
vanhempi
commit
6fd33341cf
16 muutettua tiedostoa jossa 1811 lisäystä ja 163 poistoa
  1. +8
    -1
      .claude/settings.local.json
  2. +5
    -158
      App/Controllers/Kit/KitController.asp
  3. +164
    -0
      App/DomainModels/PurpleEnvelopeReportHelper.asp
  4. +0
    -2
      App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp
  5. +34
    -2
      App/app.config.asp
  6. BIN
      ImportService/TrackingDataImport.vbs
  7. +386
    -0
      Tests/TestCase_PurpleEnvelopeReport.asp
  8. +2
    -0
      Tests/Test_All.asp
  9. BIN
      Tests/TrackingDataImport_TestHarness.vbs
  10. +1
    -0
      Tests/chillkat_serial
  11. +300
    -0
      _bmad-output/implementation-artifacts/tech-spec-exportinkjetfile-integration-test.md
  12. +187
    -0
      _bmad-output/test-artifacts/automation-summary.md
  13. +248
    -0
      _bmad-output/test-artifacts/test-design-architecture.md
  14. +80
    -0
      _bmad-output/test-artifacts/test-design-progress.md
  15. +311
    -0
      _bmad-output/test-artifacts/test-design-qa.md
  16. +85
    -0
      _bmad-output/test-artifacts/test-design/workspace-handoff.md

+ 8
- 1
.claude/settings.local.json Näytä tiedosto

@@ -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\")"
]
}
}

+ 5
- 158
App/Controllers/Kit/KitController.asp Näytä tiedosto

@@ -1,6 +1,7 @@
<% Option Explicit %>
<!--#include file="../../include_all.asp"-->
<!--#include file="../../ViewModels/KitViewModels.asp"-->
<!--#include file="../../DomainModels/PurpleEnvelopeReportHelper.asp"-->
<%
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
%> <!--#include file="../../Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp"--> <%
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


+ 164
- 0
App/DomainModels/PurpleEnvelopeReportHelper.asp Näytä tiedosto

@@ -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
%>

+ 0
- 2
App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp Näytä tiedosto

@@ -105,8 +105,6 @@
<%= HTML.Hidden("Id", Model.Kit.ID) %>
<%= HTML.DropDownListExt("OutboundSTID","hmm",Model.StidDropDown,"STID","OPTION",Array("Class","form-select")) %>
<p></p>
<%= HTML.Checkbox("InBoundTracking",0) %><strong>Inbound Tracking</strong>
<p></p>
<p><%= HTML.Button("submit", "<i class='glyphicon glyphicon-ok'></i> Save", "btn-primary") %></p>
</form>
<p></p>


+ 34
- 2
App/app.config.asp Näytä tiedosto

@@ -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
%>
%>

BIN
ImportService/TrackingDataImport.vbs Näytä tiedosto


+ 386
- 0
Tests/TestCase_PurpleEnvelopeReport.asp Näytä tiedosto

@@ -0,0 +1,386 @@
<!--#include file="../App/ViewModels/KitViewModels.asp"-->
<!--#include file="../App/DAL/lib.DAL.asp"-->
<!--#include file="../App/DomainModels/JurisdictionRepository.asp"-->
<!--#include file="../App/DomainModels/KitRepository.asp"-->
<!--#include file="../App/DomainModels/InkjetRecordsRepository.asp"-->
<!--#include file="../App/DomainModels/PurpleEnvelopeReportHelper.asp"-->
<%
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
%>

+ 2
- 0
Tests/Test_All.asp Näytä tiedosto

@@ -9,6 +9,7 @@ Option Explicit
<!--#include file="TestCase_Automapper_DynMap.asp"-->
<!--#include file="TestCase_StringBuilder.asp"-->
<!--#include file="TestCase_HtmlHelperDropdownLists.asp"-->
<!--#include file="TestCase_PurpleEnvelopeReport.asp"-->

<%
'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
%>

BIN
Tests/TrackingDataImport_TestHarness.vbs Näytä tiedosto


+ 1
- 0
Tests/chillkat_serial Näytä tiedosto

@@ -0,0 +1 @@
KENTCM.CB1022025_RGzBPM5J655e

+ 300
- 0
_bmad-output/implementation-artifacts/tech-spec-exportinkjetfile-integration-test.md Näytä tiedosto

@@ -0,0 +1,300 @@
---
title: 'ExportInkjetFile Integration Test'
slug: 'exportinkjetfile-integration-test'
created: '2026-03-17'
status: 'Implementation Complete'
stepsCompleted: [1, 2, 3, 4]
tech_stack: ['VBScript', 'ADODB', 'Chilkat_9_5_0.Csv', 'Access MDB/Jet']
files_to_modify: ['Tests/TrackingDataImport_TestHarness.vbs']
code_patterns: ['LoadFunctions+ExecuteGlobal isolation', 'ADODB connection to MDB', 'Chilkat CSV read-back']
test_patterns: ['Integration test with real MDB fixture', 'Assert file output + DB side effect']
---

# Tech-Spec: ExportInkjetFile Integration Test

**Created:** 2026-03-17

## Overview

### Problem Statement

`ExportInkjetFile(KitID)` produces the inkjet operator CSV used on press day. It has zero automated coverage. The function joins InkjetRecords + KitLabels + Colors from an Access MDB, builds a Chilkat CSV with 22 columns, writes it to disk, then marks Kit.Status='Done'. A regression here causes silent bad output with no test catching it.

### Solution

Add an ADODB integration test directly to `Tests/TrackingDataImport_TestHarness.vbs`. The harness opens `Data/webdata - Copy.mdb` via ADODB, calls `ExportInkjetFile` on a real KitID, and asserts CSV structure (file exists, 22 columns, correct headers) plus ~10 row-level field values and the DB side-effect (Kit.Status='Done', InkJetJob=1).

### Scope

**In Scope:**
- Set up integration test globals in harness: `oConn`, `ConnectionString` (pointing to `Data/webdata - Copy.mdb`), `ExportDirectory` (temp path under `Tests/`)
- Load `GetSetting` and `ExportInkjetFile` into the `functionNames` array
- Query MDB to discover a real KitID that has InkjetRecords
- Call `ExportInkjetFile(KitID)`
- Assert: CSV file exists at expected path
- Assert: 22 column headers correct (spot-check cols 0, 5, 8, 11, 19, 20, 21)
- Assert: CSV row count = InkjetRecords count for that KitID
- Assert: First 10 rows — "Ballot Number" has leading zeros stripped, "Matching Code" starts with JCode
- Assert: Kit.Status = 'Done' and Kit.InkJetJob = 1 after call
- Cleanup: delete temp export folder

**Out of Scope:**
- Pure logic extraction from `ExportInkjetFile` (not viable — all field assembly is inline with recordset reads; refactoring would be required)
- Seeding fixture data (real kit + inkjet records already exist in `Data/webdata - Copy.mdb`)
- Office copies rows (separate conditional path; can be added later if a Kit with OfficeCopiesAmount > 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/<JobNumber>-<Name>/`, 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.

+ 187
- 0
_bmad-output/test-artifacts/automation-summary.md Näytä tiedosto

@@ -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)

+ 248
- 0
_bmad-output/test-artifacts/test-design-architecture.md Näytä tiedosto

@@ -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.

+ 80
- 0
_bmad-output/test-artifacts/test-design-progress.md Näytä tiedosto

@@ -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

+ 311
- 0
_bmad-output/test-artifacts/test-design-qa.md Näytä tiedosto

@@ -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)

+ 85
- 0
_bmad-output/test-artifacts/test-design/workspace-handoff.md Näytä tiedosto

@@ -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 |

Loading…
Peruuta
Tallenna

Powered by TurnKey Linux.