This commit is contained in:
lifestorm
2024-08-04 23:12:27 +03:00
parent 0e770b2b49
commit ba1fc01b16
7084 changed files with 2173495 additions and 14 deletions

View File

@@ -0,0 +1,12 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
AddCSLuaFile( "includes/modules/pon.lua" )
include( "gm_express/sh_init.lua" )

View File

@@ -0,0 +1,63 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
express._receiverMadeQueue = {}
express._canSendReceiverMade = false
net.Receive( "express_access", function()
express:SetAccess( net.ReadString() )
express:_sendReceiversMadeQueue()
end )
function express:_sendReceiversMadeQueue()
express._canSendReceiverMade = true
local messages = table.GetKeys( express._receiverMadeQueue )
express:_alertReceiversMade( unpack( messages ) )
end
function express:_alertReceiversMade( ... )
local names = { ... }
local receiverCount = #names
net.Start( "express_receivers_made" )
net.WriteUInt( receiverCount, 8 )
for i = 1, receiverCount do
net.WriteString( names[i] )
end
net.SendToServer()
end
-- Registers a basic receiver --
function express.Receive( message, cb )
express:_setReceiver( message, cb )
if not express._canSendReceiverMade then
express._receiverMadeQueue[message] = true
return
end
express:_alertReceiversMade( message )
end
-- Calls the main _send function but passes nil for the recipient --
function express.Send( message, data, onProof )
express:_send( message, data, nil, onProof )
end
function express:SetExpected( hash, cb )
self._awaitingProof[hash] = cb
end

View File

@@ -0,0 +1,278 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
AddCSLuaFile()
express.version = 1
express.revision = 1
express._putCache = {}
express._maxCacheTime = ( 24 - 1 ) * 60 * 60 -- TODO: Get this from the server, similar to the version check
express._waitingForAccess = {}
express.domain = CreateConVar(
"express_domain", "gmod.express", FCVAR_ARCHIVE + FCVAR_REPLICATED, "The domain of the Express server"
)
-- Useful for self-hosting if you need to set express_domain to localhost
-- and direct clients to a global IP/domain to hit the same service
express.domain_cl = CreateConVar(
"express_domain_cl", "", FCVAR_ARCHIVE + FCVAR_REPLICATED, "The client-specific domain of the Express server. If empty, express_domain will be used."
)
express.sendDelay = CreateConVar(
"express_send_delay", 0.15, FCVAR_ARCHIVE + FCVAR_REPLICATED, "How long to wait (in seconds) before sending the Express Message ID to the recipient (longer delays will result in increased reliability)"
)
-- Runs the correct net Send function based on the realm --
function express.shSend( target )
if CLIENT then
net.SendToServer()
else
net.Send( target )
end
end
-- Returns the correct domain based on the realm and convars --
function express:getDomain()
local domain = self.domain:GetString()
if SERVER then return domain end
local clDomain = self.domain_cl:GetString()
if clDomain ~= "" then return clDomain end
return domain
end
-- Creates the base of the API URL from the protocol, domain, and version --
function express:makeBaseURL()
local protocol = self._protocol
local domain = self:getDomain()
return string.format( "%s://%s/v%d", protocol, domain, self.version )
end
-- Creates a full URL with the given access token --
function express:makeAccessURL( action, ... )
local url = self:makeBaseURL()
local args = { action, self.access, ... }
return url .. "/" .. table.concat( args, "/" )
end
-- Sets the access token and runs requests that were waiting --
function express:SetAccess( access )
self.access = access
local waiting = self._waitingForAccess
for _, callback in ipairs( waiting ) do
callback()
end
self._waitingForAccess = {}
end
-- Checks the version of the API and alerts of a mismatch --
function express.CheckRevision()
local suffix = " on version check! This is bad!"
local err = function( msg )
return "Express: " .. msg .. suffix
end
local url = express:makeBaseURL() .. "/revision"
local success = function( body, _, _, code )
assert( code >= 200 and code < 300, err( "Invalid response code (" .. code .. ")" ) )
local dataHolder = util.JSONToTable( body )
assert( dataHolder, err( "Invalid JSON response" ) )
local revision = dataHolder.revision
assert( revision, err( "Invalid JSON response" ) )
local current = express.revision
if revision ~= current then
error( err( "Revision mismatch! Expected " .. current .. ", got " .. revision ) )
end
end
http.Fetch( url, success, function( message )
error( err( message ) )
end, express.jsonHeaders )
end
-- Runs the main :Get function, or queues the request if no access token is set --
function express:_get( id, cb )
if self.access then
return self:Get( id, cb )
end
table.insert( self._waitingForAccess, function()
self:Get( id, cb )
end )
end
-- Runs the main :GetSize function, or queues the request if no access token is set --
-- FIXME: If this gets delayed because it doesn't have an access token, the PreDl Receiver will not be able to stop the download --
function express:_getSize( id, cb )
if self.access then
return self:GetSize( id, cb )
end
table.insert( self._waitingForAccess, function()
self:GetSize( id, cb )
end )
end
---Encodes and compresses the given data, then sends it to the API if not already cached
function express:_put( data, cb )
if table.Count( data ) == 0 then
error( "Express: Tried to send empty data!" )
end
data = pon.encode( data )
if string.len( data ) > self._maxDataSize then
data = "<enc>" .. util.Compress( data )
assert( data, "Express: Failed to compress data!" )
local dataLen = string.len( data )
if dataLen > self._maxDataSize then
error( "Express: Data too large (" .. dataLen .. " bytes)" )
end
end
local hash = util.SHA1( data )
local cached = self._putCache[hash]
if cached then
local cachedAt = cached.cachedAt
if os.time() <= ( cachedAt + self._maxCacheTime ) then
-- Force the callback to run asynchronously for consistency
timer.Simple( 0, function()
cb( cached.id, hash )
end )
return
end
end
local function wrapCb( id )
self._putCache[hash] = { id = id, cachedAt = os.time() }
cb( id, hash )
end
if self.access then
return self:Put( data, wrapCb )
end
table.insert( self._waitingForAccess, function()
self:Put( data, wrapCb )
end )
end
-- TODO: Fix GLuaTest so we can actually test this function...
-- Creates a contextual callback for the :_put endpoint, delaying the notification to the recipient(s) --
function express:_putCallback( message, plys, onProof )
return function( id, hash )
if onProof then
self:SetExpected( hash, onProof, plys )
end
-- Cloudflare isn't fulfilling their promise that the first lookup in
-- each region will "search" for the target key in K/V if it has't been cached yet.
-- This delay makes it more likely that the data will have "settled" into K/V before the first lookup
-- (Once it's cached as a 404, it'll stay that way for about 60 seconds)
timer.Simple( self.sendDelay:GetFloat(), function()
net.Start( "express" )
net.WriteString( message )
net.WriteString( id )
net.WriteBool( onProof ~= nil )
express.shSend( plys )
end )
end
end
-- Calls the _put function with a contextual callback --
function express:_send( message, data, plys, onProof )
self:_put( data, self:_putCallback( message, plys, onProof ) )
end
-- Assigns a callback to the given message --
function express:_setReceiver( message, cb )
message = string.lower( message )
self._receivers[message] = cb
end
-- Returns the receiver set for the given message --
function express:_getReceiver( message )
message = string.lower( message )
return self._receivers[message]
end
-- Returns the pre-download receiver set for the given message --
function express:_getPreDlReceiver( message )
message = string.lower( message )
return self._preDlReceivers[message]
end
-- Returns a realm-specific timeout value for HTTP requests --
function express:_getTimeout()
return CLIENT and 240 or 60
end
-- Ensures that the given HTTP response code indicates a succcessful request --
function express._checkResponseCode( code )
local isOk = isnumber( code ) and code >= 200 and code < 300
if isOk then return end
error( "Express: Invalid response code (" .. tostring( code ) .. ")" )
end
-- Attempts to re-register with the new domain, and then verifies its version --
cvars.AddChangeCallback( "express_domain", function()
express._putCache = {}
if SERVER then express:Register() end
express:CheckRevision()
end, "domain_check" )
-- Both client and server should check the version on startup so that errors are caught early --
cvars.AddChangeCallback( "express_domain_cl", function( _, _, new )
if CLIENT then express._putCache = {} end
if new == "" then return end
express:CheckRevision()
end, "domain_check" )
hook.Add( "ExpressLoaded", "Express_HTTPInit", function()
hook.Add( "Tick", "Express_RevisionCheck", function()
hook.Remove( "Tick", "Express_RevisionCheck" )
if SERVER then express:Register() end
express:CheckRevision()
end )
end )

View File

@@ -0,0 +1,227 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
AddCSLuaFile()
require( "pon" )
if SERVER then
util.AddNetworkString( "express" )
util.AddNetworkString( "express_proof" )
util.AddNetworkString( "express_receivers_made" )
end
express = {}
express._receivers = {}
express._protocol = "http"
express._awaitingProof = {}
express._preDlReceivers = {}
express._maxDataSize = 24 * 1024 * 1024
express._jsonHeaders = { ["Content-Type"] = "application/json" }
express._bytesHeaders = { ["Accept"] = "application/octet-stream" }
-- Removes a receiver --
function express.ClearReceiver( message )
message = string.lower( message )
express._receivers[message] = nil
end
-- Registers a PreDownload receiver --
function express.ReceivePreDl( message, preDl )
message = string.lower( message )
express._preDlReceivers[message] = preDl
end
-- Retrieves and parses the data for given ID --
function express:Get( id, cb, _attempts )
_attempts = _attempts or 0
local url = self:makeAccessURL( "read", id )
local success = function( code, body )
if code == 404 then
assert( _attempts <= 35, "express:Get() failed to retrieve data after 35 attempts: " .. id )
timer.Simple( 0.125 * _attempts, function()
self:Get( id, cb, _attempts + 1 )
end )
return
end
express._checkResponseCode( code )
if _attempts > 0 then
print( "express:Get() succeeded after " .. _attempts .. " attempts: " .. id )
end
if string.StartWith( body, "<enc>" ) then
body = util.Decompress( string.sub( body, 6 ) )
if ( not body ) or #body == 0 then
error( "Express: Failed to decompress data for ID '" .. id .. "'." )
end
end
local hash = util.SHA1( body )
local decodedData = pon.decode( body )
cb( decodedData, hash )
end
HTTP( {
method = "GET",
url = url,
success = success,
failed = error,
headers = self._bytesHeaders,
timeout = self:_getTimeout()
} )
end
-- Asks the API for this ID's data's size --
function express:GetSize( id, cb )
local url = self:makeAccessURL( "size", id )
local success = function( code, body )
express._checkResponseCode( code )
local sizeHolder = util.JSONToTable( body )
assert( sizeHolder, "Invalid JSON" )
local size = sizeHolder.size
if not size then
print( "Express: Failed to get size for ID '" .. id .. "'.", code )
print( body )
end
assert( size, "No size data" )
cb( tonumber( size ) )
end
HTTP( {
method = "GET",
url = url,
success = success,
failed = error,
headers = self._jsonHeaders,
timeout = self:_getTimeout()
} )
end
-- Given prepared data, sends it to the API --
function express:Put( data, cb )
local success = function( code, body )
express._checkResponseCode( code )
local response = util.JSONToTable( body )
assert( response, "Invalid JSON" )
assert( response.id, "No ID returned" )
cb( response.id )
end
HTTP( {
method = "POST",
url = self:makeAccessURL( "write" ),
body = data,
success = success,
failed = error,
headers = {
["Content-Length"] = #data,
["Accept"] = "application/json"
},
type = "application/octet-stream",
timeout = CLIENT and 240 or 60
} )
end
-- Runs the express receiver for the given message --
function express:Call( message, ply, data )
local cb = self:_getReceiver( message )
if not cb then return end
if CLIENT then return cb( data ) end
if SERVER then return cb( ply, data ) end
end
-- Runs the express pre-download receiver for the given message --
function express:CallPreDownload( message, ply, id, size, needsProof )
local cb = self:_getPreDlReceiver( message )
if not cb then return end
if CLIENT then return cb( message, id, size, needsProof ) end
if SERVER then return cb( message, ply, id, size, needsProof ) end
end
-- Handles a net message containing an ID to download from the API --
function express.OnMessage( _, ply )
local message = net.ReadString()
if not express:_getReceiver( message ) then
error( "Express: Received a message that has no listener! (" .. message .. ")" )
end
local id = net.ReadString()
local needsProof = net.ReadBool()
local function makeRequest( size )
if size then
local check = express:CallPreDownload( message, ply, id, size, needsProof )
if check == false then return end
end
express:_get( id, function( data, hash )
express:Call( message, ply, data )
if not needsProof then return end
net.Start( "express_proof" )
net.WriteString( hash )
express.shSend( ply )
end )
end
if express:_getPreDlReceiver( message ) then
return express:_getSize( id, makeRequest )
end
makeRequest()
end
-- Handles a net message containing a proof of data download --
function express.OnProof( _, ply )
-- Server prefixes the hash with the player's Steam ID
local prefix = ply and ply:SteamID64() .. "-" or ""
local hash = prefix .. net.ReadString()
local cb = express._awaitingProof[hash]
if not cb then return end
cb( ply )
express._awaitingProof[hash] = nil
end
net.Receive( "express", express.OnMessage )
net.Receive( "express_proof", express.OnProof )
include( "sh_helpers.lua" )
if SERVER then
include( "sv_init.lua" )
AddCSLuaFile( "cl_init.lua" )
else
include( "cl_init.lua" )
end
hook.Add( "CreateTeams", "ExpressLoaded", function()
hook.Run( "ExpressLoaded" )
end )

View File

@@ -0,0 +1,94 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
require( "playerload" )
util.AddNetworkString( "express_access" )
-- Registers a basic receiver --
function express.Receive( message, cb )
express:_setReceiver( message, cb )
end
-- Broadcasts the given data to all connected players --
function express.Broadcast( message, data, onProof )
express.Send( message, data, player.GetAll(), onProof )
end
-- Registers with the current API, storing and distributing access tokens --
function express.Register()
-- All stored items expire after a day
-- That includes tokens, so we need
-- to re-register if we make it this far
local oneDay = 60 * 60 * 24
timer.Create( "Express_Register", oneDay, 0, express.Register )
local url = express:makeBaseURL() .. "/register"
http.Fetch( url, function( body, _, _, code )
express._checkResponseCode( code )
local response = util.JSONToTable( body )
assert( response, "Invalid JSON" )
assert( response.server, "No server access token" )
assert( response.client, "No client access token" )
express:SetAccess( response.server )
express._clientAccess = response.client
if player.GetCount() == 0 then return end
net.Start( "express_access" )
net.WriteString( express._clientAccess )
net.Broadcast()
end, error, express.jsonHeaders )
end
-- Passthrough for the shared _send function --
function express.Send( ... )
express:_send( ... )
end
-- Sets a callback for each of the recipients that will run when they provide proof --
function express:SetExpected( hash, cb, plys )
if not istable( plys ) then plys = { plys } end
for _, ply in ipairs( plys ) do
local key = ply:SteamID64() .. "-" .. hash
self._awaitingProof[key] = cb
end
end
-- Runs a hook when a player makes a new express Receiver --
function express._onReceiverMade( _, ply )
local messageCount = net.ReadUInt( 8 )
for _ = 1, messageCount do
local name = string.lower( net.ReadString() )
hook.Run( "ExpressPlayerReceiver", ply, name )
end
end
net.Receive( "express_receivers_made", express._onReceiverMade )
-- Send the player their access token as soon as it's safe to do so --
function express._onPlayerLoaded( ply )
net.Start( "express_access" )
net.WriteString( express._clientAccess )
net.Send( ply )
end
hook.Add( "PlayerFullLoad", "Express_PlayerReady", express._onPlayerLoaded )

View File

@@ -0,0 +1,23 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local loadQueue = {}
hook.Add( "PlayerInitialSpawn", "GM_FullLoadQueue", function( ply )
loadQueue[ply] = true
end )
hook.Add( "SetupMove", "GM_FullLoadInit", function( ply, _, cmd )
if not loadQueue[ply] then return end
if cmd:IsForced() then return end
loadQueue[ply] = nil
hook.Run( "PlayerFullLoad", ply )
end )

View File

@@ -0,0 +1,415 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[
DEVELOPMENTAL VERSION;
VERSION 1.2.2
Copyright thelastpenguin™
You may use this for any purpose as long as:
- You don't remove this copyright notice.
- You don't claim this to be your own.
- You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this.
If you modify the code for any purpose, the above still applies to the modified code.
The author is not held responsible for any damages incured from the use of pon, you use it at your own risk.
DATA TYPES SUPPORTED:
- tables - k,v - pointers
- strings - k,v - pointers
- numbers - k,v
- booleans- k,v
- Vectors - k,v
- Angles - k,v
- Entities- k,v
- Players - k,v
- PhysObj - k,v
CHANGE LOG
V 1.1.0
- Added Vehicle, NPC, NextBot, Player, Weapon
V 1.2.0
- Added custom handling for k,v tables without any array component.
V 1.2.1
- fixed deserialization bug.
THANKS TO...
- VERCAS for the inspiration.
]]
local pon = {};
_G.pon = pon;
local type, count = type, table.Count ;
local tonumber = tonumber ;
local format = string.format;
do
local type, count = type, table.Count ;
local tonumber = tonumber ;
local format = string.format;
local encode = {};
local tryCache ;
local cacheSize = 0;
encode['table'] = function( self, tbl, output, cache )
if( cache[ tbl ] )then
output[ #output + 1 ] = format('(%x)', cache[tbl] );
return ;
else
cacheSize = cacheSize + 1;
cache[ tbl ] = cacheSize;
end
local first = next(tbl, nil)
local predictedNumeric = 1
local lastKey = nil
-- starts with a numeric dealio
if first == 1 then
output[#output + 1] = '{'
for k,v in next, tbl do
if k == predictedNumeric then
predictedNumeric = predictedNumeric + 1
local tv = type(v)
if tv == 'string' then
local pid = cache[v]
if pid then
output[#output + 1] = format('(%x)', pid)
else
cacheSize = cacheSize + 1
cache[v] = cacheSize
self.string(self, v, output, cache)
end
else
self[tv](self, v, output, cache)
end
else
break
end
end
predictedNumeric = predictedNumeric - 1
else
predictedNumeric = nil
end
if predictedNumeric == nil then
output[#output + 1] = '[' -- no array component
else
output[#output + 1] = '~' -- array component came first so shit needs to happen
end
for k, v in next, tbl, predictedNumeric do
local tk, tv = type(k), type(v)
-- WRITE KEY
if tk == 'string' then
local pid = cache[ k ];
if( pid )then
output[ #output + 1 ] = format('(%x)', pid );
else
cacheSize = cacheSize + 1;
cache[ k ] = cacheSize;
self.string( self, k, output, cache );
end
else
self[tk](self, k, output, cache)
end
-- WRITE VALUE
if( tv == 'string' )then
local pid = cache[ v ];
if( pid )then
output[ #output + 1 ] = format('(%x)', pid );
else
cacheSize = cacheSize + 1;
cache[ v ] = cacheSize;
self.string( self, v, output, cache );
end
else
self[ tv ]( self, v, output, cache );
end
end
output[#output + 1] = '}'
end
-- ENCODE STRING
local gsub = string.gsub ;
encode['string'] = function( self, str, output )
--if tryCache( str, output ) then return end
local estr, count = gsub( str, ";", "\\;");
if( count == 0 )then
output[ #output + 1 ] = '\''..str..';';
else
output[ #output + 1 ] = '"'..estr..'";';
end
end
-- ENCODE NUMBER
encode['number'] = function( self, num, output )
if num%1 == 0 then
if num < 0 then
output[ #output + 1 ] = format( 'x%x;', -num );
else
output[ #output + 1 ] = format('X%x;', num );
end
else
output[ #output + 1 ] = tonumber( num )..';';
end
end
-- ENCODE BOOLEAN
encode['boolean'] = function( self, val, output )
output[ #output + 1 ] = val and 't' or 'f'
end
-- ENCODE VECTOR
encode['Vector'] = function( self, val, output )
output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';');
end
-- ENCODE ANGLE
encode['Angle'] = function( self, val, output )
output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';');
end
encode['Entity'] = function( self, val, output )
local entIndex = val == NULL and '#' or val:EntIndex();
output[ #output + 1] = 'E'..entIndex..';';
end
encode['Player'] = encode['Entity'];
encode['Vehicle'] = encode['Entity'];
encode['Weapon'] = encode['Entity'];
encode['NPC'] = encode['Entity'];
encode['NextBot'] = encode['Entity'];
encode['PhysObj'] = encode['Entity'];
encode['nil'] = function( _, _, output )
output[ #output + 1 ] = '?;';
end
setmetatable( encode, {
__index = function( self, key )
local val = rawget( self, key );
if val then return val end
ErrorNoHalt('Type: '..key..' can not be encoded. Encoded as as pass-over value.');
return rawget( self, 'nil' );
end
});
do
local empty, concat = table.Empty, table.concat ;
function pon.encode( tbl )
local output = {};
cacheSize = 0;
encode[ 'table' ]( encode, tbl, output, {} );
local res = concat( output );
return res;
end
end
end
do
local tonumber = tonumber ;
local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ;
local Vector, Angle, Entity = Vector, Angle, Entity ;
local decode = {};
decode['{'] = function( self, index, str, cache )
local cur = {};
cache[ #cache + 1 ] = cur;
local k, v, tk, tv = 1, nil, nil, nil;
while( true )do
tv = sub( str, index, index );
if( not tv or tv == '~' )then
index = index + 1;
break ;
end
if( tv == '}' )then
return index + 1, cur;
end
-- READ THE VALUE
index = index + 1;
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
k = k + 1;
end
while( true )do
tk = sub( str, index, index );
if( not tk or tk == '}' )then
index = index + 1;
break ;
end
-- READ THE KEY
index = index + 1;
index, k = self[ tk ]( self, index, str, cache );
-- READ THE VALUE
tv = sub( str, index, index );
index = index + 1;
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
end
return index, cur;
end
decode['['] = function( self, index, str, cache )
local cur = {};
cache[ #cache + 1 ] = cur;
local k, v, tk, tv = 1, nil, nil, nil;
while( true )do
tk = sub( str, index, index );
if( not tk or tk == '}' )then
index = index + 1;
break ;
end
-- READ THE KEY
index = index + 1;
index, k = self[ tk ]( self, index, str, cache );
if not k then continue end
-- READ THE VALUE
tv = sub( str, index, index );
index = index + 1;
if not self[tv] then
print('did not find type: '..tv)
end
index, v = self[ tv ]( self, index, str, cache );
cur[ k ] = v;
end
return index, cur;
end
-- STRING
decode['"'] = function( self, index, str, cache )
local finish = find( str, '";', index, true );
local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' );
index = finish + 2;
cache[ #cache + 1 ] = res;
return index, res;
end
-- STRING NO ESCAPING NEEDED
decode['\''] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local res = sub( str, index, finish - 1 )
index = finish + 1;
cache[ #cache + 1 ] = res;
return index, res;
end
-- NUMBER
decode['n'] = function( self, index, str, cache )
index = index - 1;
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1 ) );
index = finish + 1;
return index, num;
end
decode['0'] = decode['n'];
decode['1'] = decode['n'];
decode['2'] = decode['n'];
decode['3'] = decode['n'];
decode['4'] = decode['n'];
decode['5'] = decode['n'];
decode['6'] = decode['n'];
decode['7'] = decode['n'];
decode['8'] = decode['n'];
decode['9'] = decode['n'];
decode['-'] = decode['n'];
-- positive hex
decode['X'] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, num;
end
-- negative hex
decode['x'] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = -tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, num;
end
-- POINTER
decode['('] = function( self, index, str, cache )
local finish = find( str, ')', index, true );
local num = tonumber( sub( str, index, finish - 1), 16 );
index = finish + 1;
return index, cache[ num ];
end
-- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO.
decode[ 't' ] = function( self, index )
return index, true;
end
decode[ 'f' ] = function( self, index )
return index, false;
end
-- VECTOR
decode[ 'v' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local vecStr = sub( str, index, finish - 1 );
index = finish + 1;
local segs = Explode( ',', vecStr, false );
return index, Vector( segs[1], segs[2], segs[3] );
end
-- ANGLE
decode[ 'a' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local angStr = sub( str, index, finish - 1 );
index = finish + 1;
local segs = Explode( ',', angStr, false );
return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
end
-- ENTITY
decode[ 'E' ] = function( self, index, str, cache )
local finish = find( str, ';', index, true );
local num = sub( str, index, finish - 1 );
index = finish + 1;
return index, num == '#' and NULL or Entity( num );
end
-- PLAYER
decode[ 'P' ] = decode[ 'E' ];
-- NIL
decode[ '?' ] = function( _, index )
return index + 1, nil;
end
function pon.decode( data )
local _, res = decode[ sub( data, 1, 1 ) ]( decode, 2, data, {});
return res;
end
end

View File

@@ -0,0 +1,48 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
return {
groupName = "pON",
beforeAll = function()
require( "pon" )
end,
beforeEach = function( state )
state.tbl = {
1, "a", true, false, nil, {}, { {}, {}, nil },
Color( 1, 1, 1 ), Angle( 1, 1, 1 ), Vector( 1, 1, 1 ), game.GetWorld()
}
end,
cases = {
{
name = "It loads properly",
func = function()
expect( pon ).to.exist()
end
},
{
name = "It encodes a table",
func = function( state )
expect( pon.encode, state.tbl ).to.succeed()
end
},
{
name = "It decodes a pON string",
func = function( state )
local encoded = pon.encode( state.tbl )
expect( pon.decode, encoded ).to.succeed()
end
}
}
}

View File

@@ -0,0 +1,796 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
return {
groupName = "Shared Helpers",
cases = {
-- variables
{
name = "Should create necessary variables",
func = function()
expect( express.version ).to.exist()
expect( express.version ).to.beA( "number" )
expect( express.revision ).to.exist()
expect( express.revision ).to.beA( "number" )
expect( express._putCache ).to.exist()
expect( express._putCache ).to.beA( "table" )
expect( express._waitingForAccess ).to.exist()
expect( express._waitingForAccess ).to.beA( "table" )
end
},
-- express.shSend
{
name = "express.shSend calls SendToServer on CLIENT",
func = function()
_G.CLIENT = true
_G.SERVER = false
local send = stub( net, "Send" )
local sendToServer = stub( net, "SendToServer" )
express.shSend()
expect( send ).wasNot.called()
expect( sendToServer ).was.called()
end,
cleanup = function()
_G.CLIENT = false
_G.SERVER = true
end
},
{
name = "express.shSend calls Send on SERVER",
func = function()
local send = stub( net, "Send" )
local sendToServer = stub( net, "SendToServer" )
express.shSend()
expect( send ).was.called()
expect( sendToServer ).wasNot.called()
end
},
-- express.getDomain
{
name = "express.getDomain returns express_domain on SERVER",
func = function( state )
state.original_domain = express.domain
express.domain = {
GetString = function()
return "example.cfcservers.org"
end
}
expect( express:getDomain() ).to.equal( "example.cfcservers.org" )
end,
cleanup = function( state )
express.domain = state.original_domain
end
},
{
name = "express.getDomain returns express_domain on CLIENT if express_domain_cl is empty",
func = function( state )
_G.CLIENT = true
_G.SERVER = false
state.original_domain = express.domain
state.original_cl_domain = express.domain_cl
express.domain = {
GetString = function()
return "example.cfcservers.org"
end
}
express.domain_cl = {
GetString = function()
return ""
end
}
expect( express:getDomain() ).to.equal( "example.cfcservers.org" )
end,
cleanup = function( state )
_G.CLIENT = false
_G.SERVER = true
express.domain = state.original_domain
express.domain_cl = state.original_cl_domain
end
},
{
name = "express.getDomain returns express_domain_cl on CLIENT if express_domain_cl is not empty",
func = function( state )
_G.CLIENT = true
_G.SERVER = false
state.original_domain = express.domain
state.original_cl_domain = express.domain_cl
express.domain = {
GetString = function()
return "example.cfcservers.org"
end
}
express.domain_cl = {
GetString = function()
return "cl.cfcservers.org"
end
}
expect( express:getDomain() ).to.equal( "cl.cfcservers.org" )
end,
cleanup = function( state )
_G.CLIENT = false
_G.SERVER = true
express.domain = state.original_domain
express.domain_cl = state.original_cl_domain
end
},
-- express.makeBaseURL
{
name = "express.makeBaseURL makes the correct URL",
func = function( state )
state.original_protocol = express._protocol
state.original_version = express.version
express.version = "1"
express._protocol = "https"
stub( express, "getDomain" ).returns( "example.cfcservers.org" )
local expected = "https://example.cfcservers.org/v1"
local actual = express:makeBaseURL()
expect( actual ).to.equal( expected )
end,
cleanup = function( state )
express._protocol = state.original_protocol
express.version = state.original_version
end
},
-- express.makeAccessURL
{
name = "express.makeAccessURL makes the correct URL",
func = function( state )
state.original_protocol = express._protocol
state.original_domain = express.domain
state.original_version = express.version
state.original_access = express.access
express._protocol = "https"
express.domain = {
GetString = function() return "example.cfcservers.org" end
}
express.version = "1"
express.access = "access-token"
local action = "action"
local param = "param"
local expected = "https://example.cfcservers.org/v1/action/access-token/param"
local actual = express:makeAccessURL( action, param )
expect( actual ).to.equal( expected )
end,
cleanup = function( state )
express._protocol = state.original_protocol
express.domain = state.original_domain
express.version = state.original_version
express.access = state.original_access
end
},
-- express.SetAccess
{
name = "express.SetAccess sets the access token to the given value",
func = function( state )
state.original_access = state.original_access or express.access
-- Sanity check
expect( #express._waitingForAccess ).to.equal( 0 )
local access = "access-token"
express:SetAccess( access )
expect( express.access ).to.equal( access )
end,
cleanup = function( state )
express.access = state.original_access
end
},
{
name = "express.SetAccess runs pending requests",
func = function( state )
state.original_access = state.original_access or express.access
-- Sanity check
expect( #express._waitingForAccess ).to.equal( 0 )
local waitingStub = stub()
express._waitingForAccess = { waitingStub }
express:SetAccess( "access-token" )
expect( waitingStub ).was.called()
expect( #express._waitingForAccess ).to.equal( 0 )
end,
cleanup = function( state )
express.access = state.original_access
end
},
-- express.CheckRevision
{
name = "express.CheckRevision alerts if the request fails",
func = function()
local fetchStub = stub( http, "Fetch" ).with( function( _, _, failure )
expect( failure, "unsuccessful" ).to.errWith( "Express: unsuccessful on version check! This is bad!" )
end )
express.CheckRevision()
expect( fetchStub ).was.called()
end
},
{
name = "express.CheckRevision alerts if the request succeeds with a non 200-level status code",
func = function()
local fetchStub = stub( http, "Fetch" ).with( function( _, success )
expect( success, "", "nil", "nil", 418 ).to.errWith(
"Express: Invalid response code (418) on version check! This is bad!"
)
end )
express.CheckRevision()
expect( fetchStub ).was.called()
end
},
{
name = "express.CheckRevision alerts if it cannot parse the response",
func = function()
local fetchStub = stub( http, "Fetch" ).with( function( _, success )
expect( success, "", "nil", "nil", 200 ).to.errWith(
"Express: Invalid JSON response on version check! This is bad!"
)
end )
express.CheckRevision()
expect( fetchStub ).was.called()
end
},
{
name = "express.CheckRevision alerts if it cannot get a revision from the response",
func = function()
local response = util.TableToJSON( { notTheRevisionLol = "0" } )
local fetchStub = stub( http, "Fetch" ).with( function( _, success )
expect( success, response, "nil", "nil", 200 ).to.errWith(
"Express: Invalid JSON response on version check! This is bad!"
)
end )
express.CheckRevision()
expect( fetchStub ).was.called()
end
},
{
name = "express.CheckRevision alerts if the remote revision doesn't match the current addon revision",
func = function( state )
state.original_revision = state.original_revision or express.revision
express.revision = 1
local response = util.TableToJSON( { revision = 0 } )
local fetchStub = stub( http, "Fetch" ).with( function( _, success )
expect( success, response, "nil", "nil", 200 ).to.errWith(
"Express: Revision mismatch! Expected 1, got 0 on version check! This is bad!"
)
end )
express.CheckRevision()
expect( fetchStub ).was.called()
end,
cleanup = function( state )
express.revision = state.original_revision
end
},
{
name = "express.CheckRevision does nothing if the revisions match",
func = function( state )
state.original_revision = state.original_revision or express.revision
express.revision = 1
local response = util.TableToJSON( { revision = 1 } )
local fetchStub = stub( http, "Fetch" ).with( function( _, success )
expect( success, response, "nil", "nil", 200 ).to.succeed()
end )
express.CheckRevision()
expect( fetchStub ).was.called()
end,
cleanup = function( state )
express.revision = state.original_revision
end
},
-- express._get
{
name = "express._get calls express.Get if the access token is set",
func = function( state )
state.original_access = state.original_access or express.access
-- Sanity check
expect( #express._waitingForAccess ).to.equal( 0 )
express.access = "access-token"
local getStub = stub( express, "Get" )
express:_get( "id", "callback" )
expect( getStub ).was.called()
expect( #express._waitingForAccess ).to.equal( 0 )
end,
cleanup = function( state )
express.access = state.original_access
end
},
{
name = "express._get adds the request to the waiting list if the access token is not set",
func = function( state )
state.original_access = state.original_access or express.access
-- Sanity check
expect( #express._waitingForAccess ).to.equal( 0 )
express.access = nil
local getStub = stub( express, "Get" )
express:_get( "id", "callback" )
expect( getStub ).wasNot.called()
expect( #express._waitingForAccess ).to.equal( 1 )
end,
cleanup = function( state )
express.access = state.original_access
express._waitingForAccess = {}
end
},
-- express._put
{
name = "express._put encodes the given data if the access token is set",
func = function( state )
-- Sanity check
expect( table.Count( express._putCache ) ).to.equal( 0 )
state.original_access = state.original_access or express.access
express.access = "access-token"
local encode = stub( pon, "encode" ).returns( "encoded-data" )
local compress = stub( util, "Compress" ).returns( "hello" )
local putStub = stub( express, "Put" )
express:_put( { "data" }, "callback" )
expect( encode ).was.called()
expect( compress ).wasNot.called()
expect( putStub ).was.called()
end,
cleanup = function( state )
express.access = state.original_access
express._putCache = {}
end
},
{
name = "express._put compresses the given data if the access token is set and data exceeds max size",
func = function( state )
-- Sanity check
expect( table.Count( express._putCache ) ).to.equal( 0 )
state.original_putCache = state.original_putCache or express._putCache
state.original_access = state.original_access or express.access
express.access = "access-token"
local encode = stub( pon, "encode" ).returns( "encoded-data" )
local compress = stub( util, "Compress" ).returns( "hello" )
local putStub = stub( express, "Put" )
stub( util, "SHA1" ).returns( "hash" )
stub( string, "len" ).returnsSequence( { express._maxDataSize + 1, 1 } )
express:_put( { "data" }, "callback" )
expect( encode ).was.called()
expect( compress ).was.called()
expect( putStub ).was.called()
end,
cleanup = function( state )
express.access = state.original_access
express._putCache = state.original_putCache
end
},
{
name = "express._put queues the request if the access token is not set",
func = function( state )
-- Sanity check
expect( table.Count( express._putCache ) ).to.equal( 0 )
expect( #express._waitingForAccess ).to.equal( 0 )
state.original_access = state.original_access or express.access
express.access = nil
local encode = stub( pon, "encode" ).returns( "encoded-data" )
local compress = stub( util, "Compress" ).returns( "hello" )
local putStub = stub( express, "Put" )
express:_put( { "data" }, "callback" )
expect( encode ).was.called()
expect( compress ).wasNot.called()
expect( putStub ).wasNot.called()
expect( #express._waitingForAccess ).to.equal( 1 )
end,
cleanup = function( state )
express.access = state.original_access
express._waitingForAccess = {}
end
},
{
name = "express._put rejects data that is too large",
func = function( state )
state.original_access = state.original_access or express.access
state.original_maxDataSize = state.original_maxDataSize or express._maxDataSize
express._maxDataSize = 0
-- Sanity check
expect( table.Count( express._putCache ) ).to.equal( 0 )
local mockData = "hello"
local expectedBytes = #( "<enc>" .. mockData )
local putStub = stub( express, "Put" )
stub( pon, "encode" ).returns( mockData )
stub( util, "Compress" ).returns( mockData )
expect( express._put, express, { "data" }, stub() ).to.errWith(
"Express: Data too large (" .. expectedBytes .. " bytes)"
)
expect( putStub ).wasNot.called()
end,
cleanup = function( state )
express.access = state.original_access
express._maxDataSize = state.original_maxDataSize
end
},
{
name = "express._put returns the ID from the cache if the data is already cached",
async = true,
timeout = 0.2,
func = function()
-- Sanity check
expect( table.Count( express._putCache ) ).to.equal( 0 )
local mockData = { "hello" }
local mockId = "test-id"
local mockHash = "test-hash"
local mockCallback = stub()
local putStub = stub( express, "Put" )
express._putCache[mockHash] = {
id = mockId,
cachedAt = os.time()
}
stub( pon, "encode" ).returns( "encoded-data" )
stub( util, "Compress" ).returns( mockData )
stub( util, "SHA1" ).returns( mockHash )
express:_put( mockData, mockCallback )
timer.Simple( 0.1, function()
expect( putStub ).wasNot.called()
expect( mockCallback ).was.called()
done()
end )
end,
cleanup = function()
express._putCache = {}
end
},
{
name = "express._put on success, calls given callback and stores response ID in cache",
func = function( state )
-- Sanity check
expect( table.Count( express._putCache ) ).to.equal( 0 )
state.original_access = state.original_access or express.access
express.access = "access-token"
local mockData = "hello"
local mockId = "test-id"
local mockHash = "test-hash"
local mockCallback = stub()
local putStub = stub( express, "Put" ).with( function( _, _, cb )
cb( mockId )
end )
stub( pon, "encode" ).returns( "encoded-data" )
stub( util, "Compress" ).returns( mockData )
stub( util, "SHA1" ).returns( mockHash )
express:_put( { "data" }, mockCallback )
expect( putStub ).was.called()
expect( mockCallback ).was.called()
local actualCached = express._putCache[mockHash]
expect( actualCached ).to.exist()
expect( actualCached.id ).to.equal( mockId )
end,
cleanup = function( state )
express._putCache = {}
express.access = state.original_access
end
},
-- express:_getSize
{
name = "express:_getSize calls express:GetSize if access token is set",
func = function( state )
state.original_access = state.original_access or express.access
express.access = "access-token"
local getSizeStub = stub( express, "GetSize" )
express:_getSize( "id", stub() )
expect( getSizeStub ).was.called()
end,
cleanup = function( state )
express.access = state.original_access
end
},
{
name = "express:_getSize queues the GetSize call if access token is not set",
func = function( state )
-- Sanity check
expect( #express._waitingForAccess ).to.equal( 0 )
state.original_access = state.original_access or express.access
express.access = nil
local getSizeStub = stub( express, "GetSize" )
express:_getSize( "id", stub() )
expect( getSizeStub ).wasNot.called()
expect( #express._waitingForAccess ).to.equal( 1 )
end,
cleanup = function( state )
express.access = state.original_access
express._waitingForAccess = {}
end
},
-- express:_send
{
name = "express._send calls _putCallback",
func = function()
local putCallback = stub()
stub( express, "_putCallback" ).returns( putCallback )
local putStub = stub( express, "_put" ).with( function( _, _, cb )
cb( "test-id", "test-hash" )
end )
express:_send( "test-message", "test-data", {}, stub() )
expect( putStub ).was.called()
expect( putCallback ).was.called()
end
},
-- express._getReceiver
{
name = "express._getReceiver returns the valid receiver for the given message",
func = function( state )
state.original_receivers = state.original_receivers or express._receivers
express._receivers = {
["test-message"] = "test-receiver"
}
local receiver = express:_getReceiver( "test-message" )
expect( receiver ).to.equal( "test-receiver" )
end,
cleanup = function( state )
express._receivers = state.original_receivers
end
},
{
name = "express._getReceiver returns the valid receiver for the given message, regardless of casing",
func = function( state )
state.original_receivers = state.original_receivers or express._receivers
express._receivers = {
["test-message"] = "test-receiver"
}
local receiver = express:_getReceiver( "TEST-MESSAGE" )
expect( receiver ).to.equal( "test-receiver" )
end,
cleanup = function( state )
express._receivers = state.original_receivers
end
},
{
name = "express._getReceiver returns nil if no receiver exists for the given message",
func = function( state )
state.original_receivers = state.original_receivers or express._receivers
express._receivers = {}
local receiver = express:_getReceiver( "test-message" )
expect( receiver ).to.beNil()
end,
cleanup = function( state )
express._receivers = state.original_receivers
end
},
-- express._getPreDlReceiver
{
name = "express._getPreDlReceiver returns the valid receiver for the given message",
func = function( state )
state.original_preDlReceivers = state.original_preDlReceivers or express._preDlReceivers
express._preDlReceivers = {
["test-message"] = "test-receiver"
}
local receiver = express:_getPreDlReceiver( "test-message" )
expect( receiver ).to.equal( "test-receiver" )
end,
cleanup = function( state )
express._preDlReceivers = state.original_preDlReceivers
end
},
{
name = "express._getPreDlReceiver returns the valid receiver for the given message, regardless of casing",
func = function( state )
state.original_preDlReceivers = state.original_preDlReceivers or express._preDlReceivers
express._preDlReceivers = {
["test-message"] = "test-receiver"
}
local receiver = express:_getPreDlReceiver( "TEST-MESSAGE" )
expect( receiver ).to.equal( "test-receiver" )
end,
cleanup = function( state )
express._preDlReceivers = state.original_preDlReceivers
end
},
{
name = "express._getPreDlReceiver returns nil if no receiver exists for the given message",
func = function( state )
state.original_preDlReceivers = state.original_preDlReceivers or express._preDlReceivers
express._preDlReceivers = {}
local receiver = express:_getPreDlReceiver( "test-message" )
expect( receiver ).to.beNil()
end,
cleanup = function( state )
express._preDlReceivers = state.original_preDlReceivers
end
},
-- express_domain callback
{
name = "express_domain callback calls express.Register and express.CheckRevision",
func = function()
local callbacks = cvars.GetConVarCallbacks( "express_domain" )
expect( callbacks ).to.exist()
expect( #callbacks ).to.equal( 1 )
local firstCallback = callbacks[1]
local callbackFunc = firstCallback[1]
expect( callbackFunc ).to.beA( "function" )
expect( firstCallback[2] ).to.equal( "domain_check" )
local registerStub = stub( express, "Register" )
local checkRevisionStub = stub( express, "CheckRevision" )
callbackFunc()
expect( registerStub ).was.called()
expect( checkRevisionStub ).was.called()
end
},
-- express:_checkResponseCode
{
name = "express._checkResponseCode succeeds if the response code is 200",
func = function()
expect( express._checkResponseCode, 200 ).to.succeed()
end
},
{
name = "express._checkResponseCode throws an error if the response code is under 200",
func = function()
expect( express._checkResponseCode, 199 ).to.errWith( "Express: Invalid response code (199)" )
end
},
{
name = "express._checkResponseCode throws an error if the response code is over 300",
func = function()
expect( express._checkResponseCode, 420 ).to.errWith( "Express: Invalid response code (420)" )
end
},
{
name = "express._checkResponseCode throws an error if the response code is nil",
func = function()
expect( express._checkResponseCode, nil ).to.errWith( "Express: Invalid response code (nil)" )
end
},
-- express._getTimeout
{
name = "express._getTimeout returns 240 on CLIENT",
func = function()
_G.CLIENT = true
_G.SERVER = false
local timeout = express:_getTimeout()
expect( timeout ).to.equal( 240 )
end,
cleanup = function()
_G.CLIENT = false
_G.SERVER = true
end
},
{
name = "express._getTimeout returns 60 on SERVER",
func = function()
local timeout = express:_getTimeout()
expect( timeout ).to.equal( 60 )
end
}
}
}

View File

@@ -0,0 +1,433 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
return {
groupName = "Shared Main",
beforeEach = function()
stub( express, "makeAccessURL" ).returns( "https://gmod.express/v1/action/access-token" )
end,
cases = {
{
name = "Sets up necessary variables and hooks",
func = function()
expect( express ).to.exist()
expect( express ).to.beA( "table" )
expect( express._receivers ).to.exist()
expect( express._receivers ).to.beA( "table" )
expect( express._protocol ).to.exist()
expect( string.StartWith( express._protocol, "http" ) ).to.beTrue()
expect( express._maxDataSize ).to.exist()
expect( express._maxDataSize ).to.beA( "number" )
expect( express._maxDataSize ).to.beLessThan( ( 24 * 1024 * 1024 ) + 1 )
expect( express._jsonHeaders ).to.exist()
expect( express._jsonHeaders ).to.beA( "table" )
expect( express._jsonHeaders["Content-Type"] ).to.equal( "application/json" )
expect( express._bytesHeaders ).to.exist()
expect( express._bytesHeaders ).to.beA( "table" )
expect( express._bytesHeaders["Accept"] ).to.equal( "application/octet-stream" )
expect( express.domain ).to.exist()
expect( tostring( express.domain ) ).to.equal( tostring( GetConVar( "express_domain" ) ) )
expect( express.domain_cl ).to.exist()
expect( tostring( express.domain_cl ) ).to.equal( tostring( GetConVar( "express_domain_cl" ) ) )
expect( net.Receivers["express"] ).to.exist()
expect( net.Receivers["express_proof"] ).to.exist()
expect( net.Receivers["express_receivers_made"] ).to.exist()
end
},
-- express:_setReceiver
{
name = "express:_setReceiver adds the given callback to the receivers table and normalizes the name",
func = function( state )
state.original_receivers = table.Copy( express._receivers )
express._receivers = {}
local callback = stub()
express:_setReceiver( "TEST-MESSAGE", callback )
expect( express._receivers["test-message"] ).to.equal( callback )
end,
cleanup = function( state )
express._receivers = state.original_receivers
end
},
-- express.ClearReceiver
{
name = "express.ClearReceiver removes the callback for the given message and normalizes the name",
func = function( state )
state.original_receivers = table.Copy( express._receivers )
express._receivers = {}
express.Receive( "test-message", stub() )
express.ClearReceiver( "TEST-MESSAGE" )
expect( express._receivers["test-message"] ).toNot.exist()
end,
cleanup = function( state )
express._receivers = state.original_receivers
end
},
-- express.ReceivePreDl
{
name = "express.ReceivePreDl adds the given callback to the receivers table and normalizes the name",
func = function( state )
state.original_preDlReceivers = table.Copy( express._preDlReceivers )
express._preDlReceivers = {}
local callback = stub()
express.ReceivePreDl( "TEST-MESSAGE", callback )
expect( express._preDlReceivers["test-message"] ).to.equal( callback )
end,
cleanup = function( state )
express._preDlReceivers = state.original_preDlReceivers
end
},
-- express.Get
{
name = "express.Get errors if the request fails",
func = function()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.failed ).to.equal( error )
end )
express:Get( "test-id", stub() )
expect( httpStub ).was.called()
end
},
{
name = "express.Get errors if the requests succeeds with a non 200-level status code",
func = function()
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.success, 418, "" ).to.errWith( "Express: Invalid response code (418)" )
end )
express:Get( "test-id", callback )
expect( httpStub ).was.called()
expect( callback ).wasNot.called()
end
},
{
name = "express.Get calls the given callback on successful response",
func = function()
stub( util, "SHA1" ).returns( "test-hash" )
stub( util, "Decompress" ).returns( "test-data" )
stub( pon, "decode" ).returns( {} )
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
options.success( 200, "" )
end )
express:Get( "test-id", callback )
expect( httpStub ).was.called()
expect( callback ).was.called()
end
},
-- express.GetSize
{
name = "express.GetSize errors if the request fails",
func = function()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.failed ).to.equal( error )
end )
express:GetSize( "test-id", stub() )
expect( httpStub ).was.called()
end
},
{
name = "express.GetSize errors if the requests succeeds with a non 200-level status code",
func = function()
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.success, 418, "" ).to.errWith( "Express: Invalid response code (418)" )
end )
express:GetSize( "test-id", callback )
expect( httpStub ).was.called()
expect( callback ).wasNot.called()
end
},
{
name = "express.GetSize errors if it cannot read the retrieved data to JSON",
func = function()
stub( util, "JSONToTable" ).returns( nil )
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.success, 200, "" ).to.errWith( "Invalid JSON" )
end )
express:GetSize( "test-id", callback )
expect( httpStub ).was.called()
expect( callback ).wasNot.called()
end
},
{
name = "express.GetSize errors if it cannot read the size from the retrieved JSON",
func = function()
stub( util, "JSONToTable" ).returns( { notTheSizeLol = 123 } )
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.success, 200, "" ).to.errWith( "No size data" )
end )
express:GetSize( "test-id", callback )
expect( httpStub ).was.called()
expect( callback ).wasNot.called()
end
},
{
name = "express.GetSize calls the given callback on successful response",
func = function()
stub( util, "JSONToTable" ).returns( { size = 123 } )
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
options.success( 200, "" )
end )
express:GetSize( "test-id", callback )
expect( httpStub ).was.called()
expect( callback ).was.called()
end
},
-- express.Put
{
name = "express.Put errors if the request fails",
func = function()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.failed ).to.equal( error )
end )
express:Put( "test-data", stub() )
expect( httpStub ).was.called()
end
},
{
name = "express.Put errors if the requests succeeds with a non 200-level status code",
func = function()
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.success, 418, "" ).to.errWith( "Express: Invalid response code (418)" )
end )
express:Put( "test-data", callback )
expect( httpStub ).was.called()
expect( callback ).wasNot.called()
end
},
{
name = "express.Put errors if it cannot read the retrieved data to JSON",
func = function()
stub( util, "JSONToTable" ).returns( nil )
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.success, 200, "" ).to.errWith( "Invalid JSON" )
end )
express:Put( "test-data", callback )
expect( httpStub ).was.called()
expect( callback ).wasNot.called()
end
},
{
name = "express.Put errors if it cannot read the ID from the retrieved JSON",
func = function()
stub( util, "JSONToTable" ).returns( { notTheIdLol = "test-id" } )
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
expect( options.success, 200, "" ).to.errWith( "No ID returned" )
end )
express:Put( "test-data", callback )
expect( httpStub ).was.called()
expect( callback ).wasNot.called()
end
},
{
name = "express.Put calls the given callback on successful response",
func = function()
stub( util, "JSONToTable" ).returns( { id = "test-id" } )
local callback = stub()
local httpStub = stub( _G, "HTTP" ).with( function( options )
options.success( 200, "" )
end )
express:Put( "test-data", callback )
expect( httpStub ).was.called()
expect( callback ).was.called()
end
},
-- express.Call
{
name = "express.Call runs the stored callback for the given message",
func = function()
local callback = stub()
stub( express, "_getReceiver" ).returns( callback )
express:Call( "test-message" )
expect( callback ).was.called()
end
},
-- express.CallPreDownload
{
name = "express.CallPreDownload runs the stored pre-download callback for the given message",
func = function()
local callback = stub()
stub( express, "_getPreDlReceiver" ).returns( callback )
express:CallPreDownload( "test-message" )
expect( callback ).was.called()
end
},
-- express.OnMessage
{
name = "express.OnMessage errors if no callback exists for the given message",
func = function()
stub( net, "ReadBool" )
stub( express, "GetSize" )
stub( express, "CallPreDownload" )
stub( express, "_get" )
stub( express, "_getPreDlReceiver" )
stub( net, "ReadString" ).returnsSequence( { "test-message" } )
stub( express, "_getReceiver" ).returns( nil )
expect( express.OnMessage ).to.errWith( "Express: Received a message that has no listener! (test-message)" )
end
},
{
name = "express.OnMessage calls GetSize if the message has a pre-download receiver",
func = function()
stub( net, "ReadBool" )
stub( express, "CallPreDownload" )
stub( express, "_get" )
stub( express, "_getReceiver" ).returns( stub() )
stub( express, "_getPreDlReceiver" ).returns( stub() )
stub( net, "ReadString" ).returnsSequence( { "test-message" } )
local getSizeStub = stub( express, "_getSize" )
express:OnMessage()
expect( getSizeStub ).was.called()
end
},
{
name = "express.OnMessage does not call GetSize if the message doesn't have a pre-download receiver",
func = function()
stub( net, "ReadBool" )
stub( express, "CallPreDownload" )
stub( express, "_get" )
stub( express, "_getReceiver" ).returns( stub() )
stub( express, "_getPreDlReceiver" )
stub( net, "ReadString" ).returnsSequence( { "test-message" } )
local getSizeStub = stub( express, "GetSize" )
express:OnMessage()
expect( getSizeStub ).wasNot.called()
end
},
{
name = "express.OnMessage calls CallPreDownload if the message has a pre-download receiver",
func = function()
end
},
{
name = "express.OnMessage does not call _get if CallPreDownload returns false",
func = function()
end
},
{
name = "express.OnMessage calls _get and Call",
func = function()
end
},
{
name = "express.OnMessage networks the data hash if proof was requested",
func = function()
end
},
{
name = "express.OnMessage does not network the data hash if proof was not requested",
func = function()
end
},
-- express.OnProof
{
name = "express.OnProof prefixes the hash with the player's steam ID if a player is given",
func = function()
end
},
{
name = "express.OnProof uses only the data hash if no player is given",
func = function()
end
},
{
name = "express.OnProof runs the stored proof callback for the given hash",
func = function()
end
}
}
}