2022-08-01 16:21:11 +00:00
{. used . }
import
std / [ unittest , options , tables , sets , times , strutils , sequtils , os ] ,
stew / byteutils ,
chronos ,
chronicles ,
sqlite3_abi
import
2022-09-13 11:36:04 +00:00
.. / .. / waku / v2 / node / storage / message / sqlite_store ,
2022-09-16 10:55:22 +00:00
.. / .. / waku / v2 / node / storage / message / message_retention_policy ,
.. / .. / waku / v2 / node / storage / message / message_retention_policy_capacity ,
2022-08-01 16:21:11 +00:00
.. / .. / waku / v2 / node / storage / sqlite ,
.. / .. / waku / v2 / protocol / waku_message ,
.. / .. / waku / v2 / utils / time ,
.. / .. / waku / v2 / utils / pagination ,
. / utils
const
DefaultPubsubTopic = " /waku/2/default-waku/proto "
DefaultContentTopic = ContentTopic ( " /waku/2/default-content/proto " )
proc newTestDatabase ( ) : SqliteDatabase =
SqliteDatabase . init ( " " , inMemory = true ) . tryGet ( )
2022-09-26 09:50:15 +00:00
proc now ( ) : Timestamp =
getNanosecondTime ( getTime ( ) . toUnixFloat ( ) )
2022-08-01 16:21:11 +00:00
proc getTestTimestamp ( offset = 0 ) : Timestamp =
2022-09-26 09:50:15 +00:00
Timestamp ( getNanosecondTime ( getTime ( ) . toUnixFloat ( ) + offset . float ) )
2022-08-01 16:21:11 +00:00
proc fakeWakuMessage (
payload = " TEST-PAYLOAD " ,
contentTopic = DefaultContentTopic ,
2022-09-26 09:50:15 +00:00
ts = now ( )
2022-08-01 16:21:11 +00:00
) : WakuMessage =
WakuMessage (
payload : toBytes ( payload ) ,
contentTopic : contentTopic ,
version : 1 ,
timestamp : ts
)
suite " SQLite message store - init store " :
test " init store " :
## Given
let database = newTestDatabase ( )
## When
2022-09-16 10:55:22 +00:00
let resStore = SqliteStore . init ( database )
2022-08-01 16:21:11 +00:00
## Then
check :
resStore . isOk ( )
let store = resStore . tryGet ( )
check :
not store . isNil ( )
## Teardown
store . close ( )
test " init store with prepopulated database with messages older than retention policy " :
# TODO: Implement initialization test cases
discard
test " init store with prepopulated database with messsage count greater than max capacity " :
# TODO: Implement initialization test cases
discard
# TODO: Add test cases to cover the store retention time fucntionality
suite " SQLite message store - insert messages " :
test " insert a message " :
## Given
const contentTopic = " test-content-topic "
let
database = newTestDatabase ( )
2022-09-13 11:36:04 +00:00
store = SqliteStore . init ( database ) . tryGet ( )
2022-08-01 16:21:11 +00:00
let message = fakeWakuMessage ( contentTopic = contentTopic )
## When
2022-09-26 09:50:15 +00:00
let resPut = store . put ( DefaultPubsubTopic , message )
2022-08-01 16:21:11 +00:00
## Then
check :
resPut . isOk ( )
let storedMsg = store . getAllMessages ( ) . tryGet ( )
check :
storedMsg . len = = 1
storedMsg . all do ( item : auto ) - > bool :
let ( _ , msg , pubsubTopic ) = item
msg . contentTopic = = contentTopic and
pubsubTopic = = DefaultPubsubTopic
## Teardown
store . close ( )
test " store capacity should be limited " :
## Given
const storeCapacity = 5
const contentTopic = " test-content-topic "
let
database = newTestDatabase ( )
2022-09-16 10:55:22 +00:00
store = SqliteStore . init ( database ) . tryGet ( )
2022-09-13 11:36:04 +00:00
retentionPolicy : MessageRetentionPolicy = CapacityRetentionPolicy . init ( capacity = storeCapacity )
2022-08-01 16:21:11 +00:00
let messages = @ [
fakeWakuMessage ( ts = getNanosecondTime ( epochTime ( ) ) + 0 ) ,
fakeWakuMessage ( ts = getNanosecondTime ( epochTime ( ) ) + 1 ) ,
fakeWakuMessage ( contentTopic = contentTopic , ts = getNanosecondTime ( epochTime ( ) ) + 2 ) ,
fakeWakuMessage ( contentTopic = contentTopic , ts = getNanosecondTime ( epochTime ( ) ) + 3 ) ,
fakeWakuMessage ( contentTopic = contentTopic , ts = getNanosecondTime ( epochTime ( ) ) + 4 ) ,
fakeWakuMessage ( contentTopic = contentTopic , ts = getNanosecondTime ( epochTime ( ) ) + 5 ) ,
fakeWakuMessage ( contentTopic = contentTopic , ts = getNanosecondTime ( epochTime ( ) ) + 6 )
]
## When
for msg in messages :
2022-09-26 09:50:15 +00:00
require store . put ( DefaultPubsubTopic , msg ) . isOk ( )
2022-09-16 10:55:22 +00:00
require retentionPolicy . execute ( store ) . isOk ( )
2022-08-01 16:21:11 +00:00
## Then
let storedMsg = store . getAllMessages ( ) . tryGet ( )
check :
storedMsg . len = = storeCapacity
storedMsg . all do ( item : auto ) - > bool :
let ( _ , msg , pubsubTopic ) = item
msg . contentTopic = = contentTopic and
pubsubTopic = = DefaultPubsubTopic
## Teardown
store . close ( )
# TODO: Review the following suite test cases
suite " Message Store " :
test " set and get works " :
## Given
let
database = newTestDatabase ( )
2022-09-13 11:36:04 +00:00
store = SqliteStore . init ( database ) . get ( )
2022-08-01 16:21:11 +00:00
2022-09-26 09:50:15 +00:00
let
2022-08-01 16:21:11 +00:00
t1 = getTestTimestamp ( 0 )
t2 = getTestTimestamp ( 1 )
t3 = high ( int64 )
var msgs = @ [
2022-09-26 09:50:15 +00:00
WakuMessage ( payload : @ [ byte 1 , 2 , 3 ] , contentTopic : DefaultContentTopic , version : uint32 ( 0 ) , timestamp : t1 ) ,
WakuMessage ( payload : @ [ byte 1 , 2 , 3 , 4 ] , contentTopic : DefaultContentTopic , version : uint32 ( 1 ) , timestamp : t2 ) ,
2022-08-01 16:21:11 +00:00
# high(uint32) is the largest value that fits in uint32, this is to make sure there is no overflow in the storage
2022-09-26 09:50:15 +00:00
WakuMessage ( payload : @ [ byte 1 , 2 , 3 , 4 , 5 ] , contentTopic : DefaultContentTopic , version : high ( uint32 ) , timestamp : t3 ) ,
2022-08-01 16:21:11 +00:00
]
2022-09-27 19:10:11 +00:00
var indexes : seq [ PagingIndex ] = @ [ ]
2022-08-01 16:21:11 +00:00
for msg in msgs :
2022-09-26 09:50:15 +00:00
require store . put ( DefaultPubsubTopic , msg , computeDigest ( msg ) , msg . timestamp ) . isOk ( )
2022-09-27 19:10:11 +00:00
let index = PagingIndex . compute ( msg , msg . timestamp , DefaultPubsubTopic )
2022-08-01 16:21:11 +00:00
indexes . add ( index )
## When
let res = store . getAllMessages ( )
## Then
check :
res . isOk ( )
let result = res . value
check :
result . len = = 3
# flags for version
var v0Flag , v1Flag , vMaxFlag : bool = false
# flags for sender timestamp
var t1Flag , t2Flag , t3Flag : bool = false
# flags for receiver timestamp
var rt1Flag , rt2Flag , rt3Flag : bool = false
2022-09-26 09:50:15 +00:00
for ( receiverTimestamp , msg , pubsubTopic ) in result :
check :
pubsubTopic = = DefaultPubsubTopic
2022-08-01 16:21:11 +00:00
# check correct retrieval of receiver timestamps
if receiverTimestamp = = indexes [ 0 ] . receiverTime : rt1Flag = true
if receiverTimestamp = = indexes [ 1 ] . receiverTime : rt2Flag = true
if receiverTimestamp = = indexes [ 2 ] . receiverTime : rt3Flag = true
check :
msg in msgs
# check the correct retrieval of versions
if msg . version = = uint32 ( 0 ) : v0Flag = true
if msg . version = = uint32 ( 1 ) : v1Flag = true
if msg . version = = high ( uint32 ) : vMaxFlag = true
# check correct retrieval of sender timestamps
if msg . timestamp = = t1 : t1Flag = true
if msg . timestamp = = t2 : t2Flag = true
if msg . timestamp = = t3 : t3Flag = true
check :
# check version
v0Flag = = true
v1Flag = = true
vMaxFlag = = true
# check sender timestamp
t1Flag = = true
t2Flag = = true
t3Flag = = true
# check receiver timestamp
rt1Flag = = true
rt2Flag = = true
rt3Flag = = true
## Cleanup
store . close ( )
test " set and get user version " :
## Given
let
database = newTestDatabase ( )
2022-09-13 11:36:04 +00:00
store = SqliteStore . init ( database ) . get ( )
2022-08-01 16:21:11 +00:00
## When
let resSetVersion = database . setUserVersion ( 5 )
let resGetVersion = database . getUserVersion ( )
## Then
check :
resSetVersion . isOk ( )
resGetVersion . isOk ( )
let version = resGetVersion . tryGet ( )
check :
version = = 5
## Cleanup
store . close ( )
test " migration " :
let
database = SqliteDatabase . init ( " " , inMemory = true ) [ ]
2022-09-13 11:36:04 +00:00
store = SqliteStore . init ( database ) [ ]
2022-08-01 16:21:11 +00:00
defer : store . close ( )
template sourceDir : string = currentSourcePath . rsplit ( DirSep , 1 ) [ 0 ]
let migrationPath = sourceDir
let res = database . migrate ( migrationPath , 10 )
check :
res . isErr = = false
let ver = database . getUserVersion ( )
check :
ver . isErr = = false
ver . value = = 10
test " number of messages retrieved by getAll is bounded by storeCapacity " :
let
contentTopic = ContentTopic ( " /waku/2/default-content/proto " )
pubsubTopic = " /waku/2/default-waku/proto "
capacity = 10
2022-09-13 11:36:04 +00:00
let
2022-09-16 10:55:22 +00:00
database = newTestDatabase ( )
store = SqliteStore . init ( database ) . tryGet ( )
2022-09-13 11:36:04 +00:00
retentionPolicy : MessageRetentionPolicy = CapacityRetentionPolicy . init ( capacity = capacity )
2022-08-01 16:21:11 +00:00
for i in 1 .. capacity :
2022-09-26 09:50:15 +00:00
let msg = WakuMessage ( payload : @ [ byte i ] , contentTopic : contentTopic , version : uint32 ( 0 ) , timestamp : Timestamp ( i ) )
require store . put ( pubsubTopic , msg ) . isOk ( )
2022-09-16 10:55:22 +00:00
require retentionPolicy . execute ( store ) . isOk ( )
## Then
2022-08-01 16:21:11 +00:00
# Test limited getAll function when store is at capacity
let resMax = store . getAllMessages ( )
check :
resMax . isOk ( )
let response = resMax . tryGet ( )
let lastMessageTimestamp = response [ ^ 1 ] [ 1 ] . timestamp
check :
response . len = = capacity # We retrieved all items
lastMessageTimestamp = = Timestamp ( capacity ) # Returned rows were ordered correctly # TODO: not representative because the timestamp only has second resolution
## Cleanup
store . close ( )
test " DB store capacity " :
let
contentTopic = ContentTopic ( " /waku/2/default-content/proto " )
pubsubTopic = " /waku/2/default-waku/proto "
capacity = 100
overload = 65
2022-09-13 11:36:04 +00:00
let
database = SqliteDatabase . init ( " " , inMemory = true ) [ ]
2022-09-16 10:55:22 +00:00
store = SqliteStore . init ( database ) . tryGet ( )
2022-09-13 11:36:04 +00:00
retentionPolicy : MessageRetentionPolicy = CapacityRetentionPolicy . init ( capacity = capacity )
2022-08-01 16:21:11 +00:00
for i in 1 .. capacity + overload :
2022-09-26 09:50:15 +00:00
let msg = WakuMessage ( payload : ( $ i ) . toBytes ( ) , contentTopic : contentTopic , version : uint32 ( 0 ) , timestamp : Timestamp ( i ) )
require store . put ( pubsubTopic , msg ) . isOk ( )
2022-09-16 10:55:22 +00:00
require retentionPolicy . execute ( store ) . isOk ( )
2022-08-01 16:21:11 +00:00
# count messages in DB
2022-09-26 09:50:15 +00:00
let numMessages = store . getMessagesCount ( ) . tryGet ( )
2022-08-01 16:21:11 +00:00
check :
# expected number of messages is 120 because
# (capacity = 100) + (half of the overflow window = 15) + (5 messages added after after the last delete)
2022-09-13 11:36:04 +00:00
# the window size changes when changing `const maxStoreOverflow = 1.3 in sqlite_store
2022-09-16 10:55:22 +00:00
numMessages = = 120
## Teardown
store . close ( )