VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "s_Database"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
' ===== Test suite for InfoDatabase =======
Option Explicit

' Public Function FindUnresolvedLink() As IteratorLinkLaw

Private db_ As InfoDatabase
Private fso_ As Scripting.FileSystemObject

Public Function Setup()
  ' Mandatory setup function
  Set db_ = New InfoDatabase
  Set fso_ = New Scripting.FileSystemObject
  Dim iPath As New API_Path: Call iPath.FromString(TEST_FOLDER).ToGlobal(ThisWorkbook.Path)
  Call db_.Init(ThisWorkbook, iPath.Text)
  Call fso_.CreateFolder(db_.docsLocation_)
End Function

Public Function Teardown()
  ' Mandatory teardown function
  Call fso_.DeleteFolder(db_.docsLocation_)
  Call ClearAll
End Function

Public Function t_Loaded()
  On Error GoTo PROPAGATE_ERROR
  
  Dim newDB As New InfoDatabase
  Call Dev_ExpectFalse(newDB.Loaded, "Uninit db")
  Call Dev_ExpectTrue(db_.Loaded, "Empty db")
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_NeedUpdate()
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_ExpectFalse(db_.NeedUpdate(), "No changes")
  
  ThisWorkbook.Sheets(HT03_SHT_DOCS).Cells(HT03_DEFAULT_FIRST_LINE, S_D_GARANT_ID) = "123"
  Call Dev_ExpectTrue(db_.NeedUpdate(), "Change doc")
  
  ThisWorkbook.Sheets(HT03_SHT_DOCS).Cells(HT03_DEFAULT_FIRST_LINE, S_D_GARANT_ID) = ""
  Call Dev_ExpectFalse(db_.NeedUpdate(), "Revert change")
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_DocumentsLocation()
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_NewCase("Uninitialized db")
  Dim newDB As New InfoDatabase
  On Error Resume Next
  Call Dev_ExpectEQ("", newDB.docsLocation_)
  Call Dev_ExpectNoError
  On Error GoTo PROPAGATE_ERROR
   
  Call Dev_ExpectEQ(ThisWorkbook.Path & "\" & HT03_DEFAULT_WORKDIR, AccessDatabase.docsLocation_, "Default folder")
  Call Dev_ExpectEQ(ThisWorkbook.Path & "\" & TEST_FOLDER, db_.docsLocation_, "Custom folder")
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_DocumentsRelativePath()
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_NewCase("Uninitialized db")
  Dim newDB As New InfoDatabase
  On Error Resume Next
  Call Dev_ExpectEQ("", newDB.DocumentsRelativePath)
  Call Dev_ExpectAnyError
  On Error GoTo PROPAGATE_ERROR
  
  Dim defaultDB As InfoDatabase: Set defaultDB = AccessDatabase
  Call Dev_ExpectEQ(".\" & HT03_DEFAULT_WORKDIR & "\", defaultDB.DocumentsRelativePath, "Default folder")
  Call Dev_ExpectEQ(".\" & TEST_FOLDER & "\", db_.DocumentsRelativePath, "Init folder")
  
  defaultDB.docsLocation_ = ThisWorkbook.Path
  Call Dev_ExpectEQ(".\", defaultDB.DocumentsRelativePath, "Local folder")
  
  Dim sGlobal$: sGlobal = "D:\DEV\testLocation"
  defaultDB.docsLocation_ = sGlobal
  Call Dev_ExpectEQ(sGlobal & "\", defaultDB.DocumentsRelativePath, "Global folder")
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_Validate()
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_NewCase("Empty db")
  Dim nEmptyHash$: nEmptyHash = db_.Hash
  Call Dev_ExpectEQ(0, db_.Count)
  Call Dev_ExpectFalse(db_.NeedUpdate)
  
  Call Dev_NewCase("Outdated db")
  ThisWorkbook.Sheets(HT03_SHT_DOCS).Cells(HT03_DEFAULT_FIRST_LINE, S_D_GARANT_ID) = "123"
  Call Dev_ExpectEQ(1, db_.Count)
  Call Dev_ExpectTrue(db_.NeedUpdate)
  
  Call Dev_NewCase("Recreated db")
  Call db_.Validate
  Dim nNewHash$: nNewHash = db_.Hash
  Call Dev_ExpectNE(nEmptyHash, nNewHash)
  Call Dev_ExpectEQ(1, db_.Count)
  Call Dev_ExpectFalse(db_.NeedUpdate)

  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_GenerateID()
  On Error GoTo PROPAGATE_ERROR
  
  Dim emptyGid$: emptyGid = db_.GenerateID("")
  Call Dev_ExpectNE(0, emptyGid, "Empty DB")
  
  Call Dev_NewCase("Seed taken")
  Dim newData As New ItemLaw: Set newData = GetDummyData
  newData.gid_ = emptyGid
  Call db_.SetItem(newData.Clone)
  Call Dev_ExpectNE(emptyGid, db_.GenerateID(""))
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_SetItem()
  On Error GoTo PROPAGATE_ERROR
  
  Dim newData As New ItemLaw: Set newData = GetDummyData
  Call Dev_ExpectFalse(db_.Contains(newData.gid_), "Empty db contains nothing")
  Call db_.SetItem(newData.Clone)
  
  Call Dev_NewCase("Add item to empty")
  Call Dev_AssertEQ(1, db_.Count)
  Call Dev_AssertTrue(db_.Contains(newData.gid_))
  Dim itLaw As IteratorLaw: Set itLaw = db_.Data(newData.gid_)
  Call Dev_ExpectEQ(newData.gid_, itLaw.ID)
  Call Dev_ExpectEQ(newData.status_, itLaw.Status)
  Call Dev_ExpectEQ(newData.type_, itLaw.DType)
  Call Dev_ExpectEQ(newData.fileName_, itLaw.FileName)
  Call Dev_ExpectEQ(newData.title_, itLaw.Title)
  Call Dev_ExpectEQ(newData.typeTxt_, itLaw.TypeText)
  Call Dev_ExpectEQ(newData.creator_, itLaw.Creator)
  Call Dev_ExpectEQ(newData.date_, itLaw.CreationDate)
  Call Dev_ExpectEQ(newData.header_, itLaw.Header)
  Call Dev_ExpectEQ(newData.attach_, itLaw.Attachment)
  Call Dev_ExpectEQ(newData.isScoped_, itLaw.IsScoped)
  Call Dev_ExpectEQ(newData.isDummy_, itLaw.IsDummy)
  Call Dev_ExpectEQ(HT03_INVALID_ROW, itLaw.LinkBase)
  Call Dev_ExpectEQ(newData.status_, db_.Cache(newData.gid_).status_)
  
  Call db_.SetItem(newData.Clone)
  Call Dev_ExpectEQ(1, db_.Count, "Add item twice")
  
  newData.status_ = T_LAWS_FRINGE
  Call db_.SetItem(newData.Clone)
  Call Dev_ExpectEQ(1, db_.Count, "Update item")
  Call Dev_ExpectEQ(newData.status_, db_.Data(newData.gid_).Status, "Update item data")
  Call Dev_ExpectEQ(newData.status_, db_.Cache(newData.gid_).status_, "Update item cache")
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_AddLinks()
  On Error GoTo PROPAGATE_ERROR
  
  Dim unallocatedArray() As String
  Dim docData As New ItemLaw: Set docData = GetDummyData
  Dim gid&: gid = docData.gid_
  Call db_.SetItem(docData.Clone)
  
  Call Dev_ExpectFalse(db_.SetLinksFor(gid, unallocatedArray), "Unallocated array")
  
  Call Dev_NewCase("Valid add links")
  Dim lnkData() As String: lnkData = GenerateLinks
  Dim lnkCount&: lnkCount = UBound(lnkData, 1) - LBound(lnkData, 1) + 1
  Call Dev_AssertTrue(db_.SetLinksFor(gid, lnkData))
  
  Dim itLnk As IteratorLinkLaw: Set itLnk = db_.LawLinks(gid)
  Call Dev_AssertNotNothing(itLnk)
  Call Dev_ExpectEQ(lnkCount, db_.CountLinks)
  Dim nItem&: nItem = 0
  Do
    nItem = nItem + 1
    Call Dev_ExpectEQ(lnkData(nItem, S_TL_DESTINATION_ID), CStr(itLnk.DestinationID))
    If Not itLnk.Increment Then _
      Exit Do
  Loop
  
  Call Dev_NewCase("Reload links")
  Dim lnkData2() As String: lnkData2 = GenerateLinks
  lnkCount = UBound(lnkData2, 1) - LBound(lnkData2, 1) + 1
  Call Dev_AssertTrue(db_.SetLinksFor(gid, lnkData2))
  Call db_.Recreate
  Call Dev_ExpectEQ(lnkCount, db_.CountLinks)
  Set itLnk = db_.LawLinks(gid)
  nItem = 0
  Do
    nItem = nItem + 1
    Call Dev_ExpectEQ(lnkData2(nItem, S_TL_DESTINATION_ID), CStr(itLnk.DestinationID))
    If Not itLnk.Increment Then _
      Exit Do
  Loop

  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_HasLinksFor()
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_ExpectFalse(db_.HasLinksFor(123), "Empty DB")
  
  Dim newData As New ItemLaw: Set newData = GetDummyData
  Dim gid&: gid = newData.gid_
  Call db_.SetItem(newData.Clone)
  Call Dev_ExpectFalse(db_.HasLinksFor(gid), "Has no links")
  Call Dev_ExpectFalse(db_.HasLinksFor(gid + 1), "Invalid gid")
  
  Call db_.SetLinksFor(gid, GenerateLinks)
  Call Dev_ExpectTrue(db_.HasLinksFor(gid), "Has links")
  
  Call Dev_NewCase("Corrupted link index")
  Dim iLaw As IteratorLaw: Set iLaw = db_.Data(gid)
  iLaw.LinkBase = HT03_INVALID_ROW
  Call db_.Recreate
  Call Dev_ExpectFalse(db_.HasLinksFor(gid))
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_GetLawStatus()
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_ExpectEQ(T_LAWS_NOTDEFINED, db_.GetLawStatus(123), "Empty DB")
  
  Dim newData As New ItemLaw: Set newData = GetDummyData
  Dim gid&: gid = newData.gid_
  newData.status_ = T_LAWS_OUTER
  Call db_.SetItem(newData.Clone)
  Call Dev_ExpectEQ(T_LAWS_OUTER, db_.GetLawStatus(gid), "Valid status")
  Call Dev_ExpectEQ(T_LAWS_NOTDEFINED, db_.GetLawStatus(gid + 1), "Invalid status")
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_GetLawInfo()
  On Error GoTo PROPAGATE_ERROR
  
  Dim iLaw As ItemLaw
  Dim newData As New ItemLaw: Set newData = GetDummyData
  Dim gid&: gid = newData.gid_
  
  Call Dev_ExpectNothing(db_.GetLawInfo(123), "Empty DB")
  
  Call db_.SetItem(newData.Clone)
  
  Call Dev_ExpectNothing(db_.GetLawInfo(gid + 1), "Ivanlid ID")
  
  Call Dev_NewCase("Valid access")
  Set iLaw = db_.GetLawInfo(gid)
  Call Dev_AssertNotNothing(iLaw)
  Call Dev_ExpectEQ(iLaw.attach_, newData.attach_)
  Call Dev_ExpectEQ(iLaw.creator_, newData.creator_)
  Call Dev_ExpectEQ(iLaw.date_, newData.date_)
  Call Dev_ExpectEQ(iLaw.fileName_, newData.fileName_)
  Call Dev_ExpectEQ(iLaw.gid_, newData.gid_)
  Call Dev_ExpectEQ(iLaw.header_, newData.header_)
  Call Dev_ExpectEQ(iLaw.isDummy_, newData.isDummy_)
  Call Dev_ExpectEQ(iLaw.isScoped_, newData.isScoped_)
  Call Dev_ExpectEQ(iLaw.number_, newData.number_)
  Call Dev_ExpectEQ(iLaw.status_, newData.status_)
  Call Dev_ExpectEQ(iLaw.title_, newData.title_)
  Call Dev_ExpectEQ(iLaw.type_, newData.type_)
  Call Dev_ExpectEQ(iLaw.typeTxt_, newData.typeTxt_)
  
  Call Dev_NewCase("Dummy")
  newData.isDummy_ = True
  Call db_.SetItem(newData.Clone)
  Call Dev_ExpectNothing(db_.GetLawInfo(gid))
  
  Call Dev_NewCase("Not loaded")
  newData.isDummy_ = False
  newData.title_ = HT03_TITLE_NOT_LOADED
  Call db_.SetItem(newData.Clone)
  Call Dev_ExpectNothing(db_.GetLawInfo(gid))

  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_FillLawsStatus()
  On Error GoTo PROPAGATE_ERROR
  
  Dim docs As New Scripting.Dictionary
  On Error Resume Next
  Call db_.FillLawsStatus(docs)
  Call Dev_ExpectNoError("Empty input")
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_NewCase("Valid input")
  Dim newData As New ItemLaw: Set newData = GetDummyData
  Dim gid&: gid = newData.gid_
  newData.status_ = T_LAWS_FRINGE
  Call db_.SetItem(newData.Clone)
  Call docs.Add(gid, "")
  Call docs.Add(gid + 1, "")
  Call db_.FillLawsStatus(docs)
  Call Dev_ExpectEQ(T_LAWS_FRINGE, docs(gid))
  Call Dev_ExpectEQ(T_LAWS_NOTDEFINED, docs(gid + 1))
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_FindByFileName()
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_ExpectNothing(db_.FindByFileName("name.docx"), "Empty DB")
  
  Dim newData As New ItemLaw: Set newData = GetDummyData
  Dim gid&: gid = newData.gid_
  Call db_.SetItem(newData.Clone)
  
  db_.Data(gid).FileName = ""
  Call Dev_ExpectNothing(db_.FindByFileName(""), "Empty name")
  
  Call Dev_NewCase("Valid find by file name")
  db_.Data(gid).FileName = newData.fileName_
  Dim it As IteratorLaw: Set it = db_.FindByFileName(newData.fileName_)
  Call Dev_AssertNotNothing(it)
  Call Dev_ExpectEQ(newData.gid_, it.ID)
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_RenameLawFile()
  On Error GoTo PROPAGATE_ERROR
  
  Dim newData As New ItemLaw: Set newData = GetDummyData
  Dim gid&: gid = newData.gid_
  newData.fileName_ = "test001.txt"
  Call db_.SetItem(newData.Clone)
  Dim sAnotherName$: sAnotherName = "test002.txt"
  Dim sFile1$: sFile1 = db_.docsLocation_ & "\" & newData.fileName_
  Dim sFile2$: sFile2 = db_.docsLocation_ & "\" & sAnotherName
  
  Call Dev_ExpectFalse(db_.RenameLawFile(gid, sAnotherName), "No file present")
  
  Dim fs As Scripting.TextStream: Set fs = fso_.CreateTextFile(sFile1)
  Call fs.Close
  
  Call Dev_ExpectFalse(db_.RenameLawFile(gid, newData.fileName_), "Same name")
  
  Call Dev_NewCase("Valid rename")
  Call Dev_ExpectTrue(db_.RenameLawFile(gid, sAnotherName), "Rename")
  Call Dev_ExpectFalse(db_.NeedUpdate, "Need update")
  Call Dev_ExpectEQ(sAnotherName, db_.Data(gid).FileName, "Check filename")
  Call Dev_ExpectFalse(fso_.FileExists(sFile1), "Delete initial file")
  Call Dev_ExpectTrue(fso_.FileExists(sFile2), "Renamed file exists")
  
  Call Dev_NewCase("Check hyperlink")
  Dim fnCell As Excel.Range: Set fnCell = ThisWorkbook.Sheets(HT03_SHT_DOCS).Cells(db_.Data(gid).Row, S_D_FILENAME)
  Call Dev_ExpectEQ(1, fnCell.Hyperlinks.Count)
  Call Dev_ExpectEQ(TEST_FOLDER & "\" & sAnotherName, fnCell.Hyperlinks(1).Address)
  
  Call Dev_NewCase("Invalid filename")
  Dim sInvalidName$: sInvalidName = "123?|.doc"
  Dim sFile3$: sFile3 = db_.docsLocation_ & "\" & sInvalidName
  Call Dev_ExpectFalse(db_.RenameLawFile(gid, sInvalidName))
  Call Dev_ExpectEQ(sAnotherName, db_.Data(gid).FileName)
  
  If fso_.FileExists(sFile1) Then _
    Call fso_.DeleteFile(sFile1)
  If fso_.FileExists(sFile2) Then _
    Call fso_.DeleteFile(sFile2)
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_IgnoreLaw()
  On Error GoTo PROPAGATE_ERROR
  
  Dim newData As New ItemLaw: Set newData = GetDummyData
  newData.status_ = T_LAWS_FRINGE
  Dim gid&: gid = newData.gid_
  Dim externalGid&: externalGid = gid + 1
  Call db_.SetItem(newData.Clone)
  Call db_.SetLinksFor(gid, GenerateLinks)
  
  Call Dev_NewCase("Ignore external document")
  Call Dev_ExpectTrue(db_.IgnoreLaw(externalGid))
  Call Dev_AssertTrue(db_.Contains(externalGid))
  Call Dev_ExpectEQ(T_LAWS_IGNORE, db_.Data(externalGid).Status)
  Call Dev_ExpectEQ(T_LAW_01_UNKNOWN, db_.Data(externalGid).DType)
  Call Dev_ExpectEQ(HT03_FILE_NOT_LOADED, db_.Data(externalGid).FileName)
  Call Dev_ExpectEQ(HT03_TITLE_NOT_LOADED, db_.Data(externalGid).Title)
  Call Dev_ExpectTrue(db_.Data(externalGid).IsDummy)
  
  Call Dev_ExpectFalse(db_.IgnoreLaw(externalGid), "Ignore already ignored")
  
  Call Dev_NewCase("Ignore internal document")
  Call Dev_ExpectTrue(db_.IgnoreLaw(gid))
  Call Dev_ExpectTrue(db_.Contains(gid))
  Call Dev_ExpectEQ(T_LAWS_IGNORE, db_.Data(gid).Status)
  Call Dev_ExpectNE(0, db_.CountLinks, "Do not delete links on ignore")
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_DeleteSomeLinksFor()
  On Error GoTo PROPAGATE_ERROR

  Dim item1 As New ItemLaw: Set item1 = GetDummyData
  Dim item2 As New ItemLaw: Set item2 = GetDummyData
  item2.gid_ = item1.gid_ + 1

  Call Dev_ExpectFalse(db_.DeleteSomeLinksFor(item1.gid_, 1, 1), "Remove links for invalid doc")

  Call db_.SetItem(item1.Clone)
  Call db_.SetItem(item2.Clone)

  Call Dev_ExpectFalse(db_.DeleteSomeLinksFor(item1.gid_, 1, 1), "Remove links for doc with no links")
  
  Dim lnks() As String: lnks = GenerateLinks(50)
  Call db_.SetLinksFor(item1.gid_, lnks)
  Call db_.SetLinksFor(item2.gid_, GenerateLinks(10))
  
  Call Dev_ExpectFalse(db_.DeleteSomeLinksFor(item1.gid_, 100, 1), "Remove links invalid start position")
  Call Dev_ExpectFalse(db_.DeleteSomeLinksFor(item1.gid_, 1, 0), "Remove links zero count")
  Call Dev_ExpectFalse(db_.DeleteSomeLinksFor(item1.gid_, 1, 100), "Remove links invalid count")
  
  Dim nLnk1&: nLnk1 = db_.Cache(item1.gid_).linkBase_
  Dim nLnk2&: nLnk2 = db_.Cache(item2.gid_).linkBase_
  
  Call Dev_NewCase("Remove some links")
  Dim delCount&: delCount = 10
  Call Dev_ExpectTrue(db_.DeleteSomeLinksFor(item1.gid_, 2, delCount))
  Call Dev_ExpectFalse(db_.NeedUpdate)
  Call Dev_ExpectEQ(nLnk1, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(nLnk2 - delCount, db_.Cache(item2.gid_).linkBase_)
  Dim iLink As IteratorLinkLaw: Set iLink = db_.LawLinks(item1.gid_)
  Call Dev_ExpectEQ(lnks(1, S_TL_DESTINATION_ID), CStr(iLink.DestinationID))
  Call iLink.Increment
  Call Dev_ExpectEQ(lnks(12, S_TL_DESTINATION_ID), CStr(iLink.DestinationID))
  
  Call Dev_NewCase("Remove all links")
  delCount = ArraySize(lnks, 1) - delCount
  Call Dev_ExpectTrue(db_.DeleteSomeLinksFor(item1.gid_, 1, delCount))
  Call Dev_ExpectFalse(db_.NeedUpdate)
  Call Dev_ExpectEQ(HT03_INVALID_ROW, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(nLnk1, db_.Cache(item2.gid_).linkBase_)
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_DeleteLinksFor()
  On Error GoTo PROPAGATE_ERROR
  
  Dim item1 As New ItemLaw: Set item1 = GetDummyData
  Dim item2 As New ItemLaw: Set item2 = GetDummyData
  item2.gid_ = item1.gid_ + 1
  Dim item3 As New ItemLaw: Set item3 = GetDummyData
  item3.gid_ = item2.gid_ + 1
  
  Call Dev_ExpectFalse(db_.DeleteLinksFor(item1.gid_), "Remove links for invalid doc")
  
  Call db_.SetItem(item1.Clone)
  Call db_.SetItem(item2.Clone)
  Call db_.SetItem(item3.Clone)
  
  Call Dev_ExpectFalse(db_.DeleteLinksFor(item1.gid_), "Remove links for doc with no links")
  
  Call db_.SetLinksFor(item1.gid_, GenerateLinks)
  Call db_.SetLinksFor(item2.gid_, GenerateLinks)
  Call db_.SetLinksFor(item3.gid_, GenerateLinks)
  
  Dim nLnk1&: nLnk1 = db_.Cache(item1.gid_).linkBase_
  Dim nLnk2&: nLnk2 = db_.Cache(item2.gid_).linkBase_
  Dim nLnk3&: nLnk3 = db_.Cache(item3.gid_).linkBase_
  
  Call Dev_NewCase("Valid remove links")
  Call Dev_ExpectTrue(db_.DeleteLinksFor(item2.gid_))
  Call Dev_ExpectFalse(db_.NeedUpdate)
  Call Dev_ExpectEQ(nLnk1, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(HT03_INVALID_ROW, db_.Cache(item2.gid_).linkBase_)
  Call Dev_ExpectEQ(nLnk2, db_.Cache(item3.gid_).linkBase_)
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_DeleteLaw()
  On Error GoTo PROPAGATE_ERROR
  
  Dim item1 As New ItemLaw: Set item1 = GetDummyData
  Dim item2 As New ItemLaw: Set item2 = GetDummyData
  item2.gid_ = item1.gid_ + 1
  Dim item3 As New ItemLaw: Set item3 = GetDummyData
  item3.gid_ = item2.gid_ + 1
  
  Call Dev_ExpectFalse(db_.DeleteLaw(item1.gid_), "Delete invalid doc")
  
  Call Dev_NewCase("Delete document with no links")
  Call db_.SetItem(item1.Clone)
  Call Dev_ExpectTrue(db_.DeleteLaw(item1.gid_))
  Call Dev_ExpectFalse(db_.Contains(item1.gid_))
  Call Dev_ExpectFalse(db_.NeedUpdate)
  
  Call db_.SetItem(item1.Clone)
  Call db_.SetItem(item2.Clone)
  Call db_.SetItem(item3.Clone)
  Call db_.SetLinksFor(item1.gid_, GenerateLinks)
  Call db_.SetLinksFor(item2.gid_, GenerateLinks)
  Call db_.SetLinksFor(item3.gid_, GenerateLinks)
  
  Dim nLnk1&: nLnk1 = db_.Cache(item1.gid_).linkBase_
  Dim nLnk2&: nLnk2 = db_.Cache(item2.gid_).linkBase_
  Dim nLnk3&: nLnk3 = db_.Cache(item3.gid_).linkBase_
  
  Call Dev_NewCase("Valid delete document")
  Call Dev_ExpectTrue(db_.DeleteLaw(item2.gid_))
  Call Dev_ExpectFalse(db_.Contains(item2.gid_))
  Call Dev_ExpectFalse(db_.NeedUpdate)
  Call Dev_ExpectEQ(nLnk1, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(nLnk2, db_.Cache(item3.gid_).linkBase_)
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_AddLinkAfter()
  On Error GoTo PROPAGATE_ERROR

  Dim item1 As New ItemLaw: Set item1 = GetDummyData
  Dim item2 As New ItemLaw: Set item2 = GetDummyData
  item2.gid_ = item1.gid_ + 1

  Call Dev_ExpectFalse(db_.AddLinkAfter(item1.gid_, 0, 1, "", "", T_LINK_DEFAULT), "Add link to invalid doc")

  Call Dev_NewCase("Add link with invalid position")
  Call db_.SetItem(item1.Clone)
  Call db_.SetItem(item2.Clone)
  Call Dev_ExpectFalse(db_.AddLinkAfter(item1.gid_, -1, item2.gid_, "", "", T_LINK_DEFAULT))
  Call Dev_ExpectFalse(db_.AddLinkAfter(item1.gid_, 10, item2.gid_, "", "", T_LINK_DEFAULT))
  
  Call Dev_NewCase("Add link to empty doc")
  Call Dev_ExpectTrue(db_.AddLinkAfter(item1.gid_, 0, item2.gid_, "subTo", "subFrom", T_LINK_DEFINITION))
  Call Dev_ExpectFalse(db_.NeedUpdate)
  Call Dev_AssertNE(HT03_INVALID_ROW, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(HT03_INVALID_ROW, db_.Cache(item2.gid_).linkBase_)
  Call Dev_ExpectEQ(item2.gid_, db_.LawLinks(item1.gid_).DestinationID)
  Call Dev_ExpectEQ("subTo", db_.LawLinks(item1.gid_).DestinationSub)
  Call Dev_ExpectEQ("subFrom", db_.LawLinks(item1.gid_).SourceSub)
  Call Dev_ExpectEQ(T_LINK_DEFINITION, db_.LawLinks(item1.gid_).LType)
  
  Dim lnks() As String: lnks = GenerateLinks(10)
  Call db_.SetLinksFor(item1.gid_, lnks)
  Call db_.SetLinksFor(item2.gid_, GenerateLinks(10))
  Dim nLnk1&: nLnk1 = db_.Cache(item1.gid_).linkBase_
  Dim nLnk2&: nLnk2 = db_.Cache(item2.gid_).linkBase_
  
  Call Dev_NewCase("Add link to middle")
  Call Dev_ExpectTrue(db_.AddLinkAfter(item1.gid_, 1, item2.gid_, "subTo", "subFrom", T_LINK_DEFINITION))
  Call Dev_ExpectFalse(db_.NeedUpdate)
  Call Dev_AssertEQ(nLnk1, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(nLnk2 + 1, db_.Cache(item2.gid_).linkBase_)
  Dim iLink As IteratorLinkLaw: Set iLink = db_.LawLinks(item1.gid_)
  Call Dev_ExpectEQ(lnks(1, S_TL_DESTINATION_ID), CStr(iLink.DestinationID))
  Call iLink.Increment
  Call Dev_ExpectEQ(item2.gid_, iLink.DestinationID)

  Call Dev_NewCase("Add link last")
  Call Dev_ExpectTrue(db_.AddLinkAfter(item1.gid_, ArraySize(lnks, 1) + 1, item2.gid_, "subTo", "subFrom", T_LINK_DEFINITION))
  Call Dev_ExpectFalse(db_.NeedUpdate)
  Call Dev_ExpectEQ(nLnk1, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(nLnk2 + 2, db_.Cache(item2.gid_).linkBase_)
  Set iLink = db_.LawLinks(item1.gid_)
  Call iLink.Increment(ArraySize(lnks, 1) + 1)
  Call Dev_ExpectEQ(item2.gid_, iLink.DestinationID)

  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function

Public Function t_ValidateLinks()
  On Error GoTo PROPAGATE_ERROR
  
  Call Dev_ExpectEQ(vbNullString, db_.ValidateLinks, "EmptyDB")
  
  Dim item1 As New ItemLaw: Set item1 = GetDummyData
  Dim item2 As New ItemLaw: Set item2 = GetDummyData
  item2.gid_ = item1.gid_ + 1
  
  Call Dev_NewCase("No links")
  Call db_.SetItem(item1.Clone)
  Call db_.SetItem(item2.Clone)
  Call Dev_ExpectEQ(vbNullString, db_.ValidateLinks)
  
  Call Dev_NewCase("Valid links")
  Call db_.SetLinksFor(item1.gid_, GenerateLinks)
  Call db_.SetLinksFor(item2.gid_, GenerateLinks)
  Call Dev_ExpectEQ(vbNullString, db_.ValidateLinks)
  
  Call Dev_NewCase("Update invalid bases")
  Dim nBase1&: nBase1 = db_.Cache(item1.gid_).linkBase_
  Dim nBase2&: nBase2 = db_.Cache(item2.gid_).linkBase_
  db_.Data(item1.gid_).LinkBase = HT03_INVALID_ROW
  db_.Data(item2.gid_).LinkBase = HT03_INVALID_ROW
  Call Dev_ExpectEQ(vbNullString, db_.ValidateLinks)
  Call Dev_ExpectEQ(nBase1, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(nBase1, db_.Data(item1.gid_).LinkBase)
  Call Dev_ExpectEQ(nBase2, db_.Cache(item2.gid_).linkBase_)
  Call Dev_ExpectEQ(nBase2, db_.Data(item2.gid_).LinkBase)
  
  Call Dev_NewCase("Update deleted links")
  Call db_.DeleteLinksFor(item1.gid_)
  nBase2 = db_.Cache(item2.gid_).linkBase_
  Call Dev_ExpectEQ(vbNullString, db_.ValidateLinks)
  Call Dev_ExpectEQ(HT03_INVALID_ROW, db_.Cache(item1.gid_).linkBase_)
  Call Dev_ExpectEQ(HT03_INVALID_ROW, db_.Data(item1.gid_).LinkBase)
  Call Dev_ExpectEQ(nBase2, db_.Cache(item2.gid_).linkBase_)
  Call Dev_ExpectEQ(nBase2, db_.Data(item2.gid_).LinkBase)
  Call db_.SetLinksFor(item1.gid_, GenerateLinks)
  
  Call Dev_NewCase("Invalid document links")
  Dim nInvalid&: nInvalid = item2.gid_ + 1
  Dim iLink As IteratorLinkAll: Set iLink = db_.Links
  Call iLink.GotoInsertion
  iLink.SourceID = nInvalid
  iLink.DestinationID = item1.gid_
  iLink.Status = T_LINKS_ADD
  iLink.LType = T_LINK_ASPECT
  Dim sLog$: sLog = db_.ValidateLinks
  Call Dev_ExpectNE(vbNullString, sLog)
  Call Dev_ExpectTrue(InStr(1, sLog, CStr(nInvalid)) <> 0)
  
  Call Dev_NewCase("Duplicate document links")
  iLink.SourceID = item2.gid_
  sLog = db_.ValidateLinks
  Call Dev_ExpectNE(vbNullString, sLog)
  Call Dev_ExpectTrue(InStr(1, sLog, CStr(item2.gid_)) <> 0)
  Call iLink.RemoveRow
  
  Exit Function
PROPAGATE_ERROR:
  Call Dev_LogError(Err.Number, Err.Description)
End Function
