From 9c828d65e6622e609ff676a73a1b120dfe140ec1 Mon Sep 17 00:00:00 2001 From: Deon George Date: Mon, 25 Nov 2024 13:46:16 +1100 Subject: [PATCH] Update TestNodeHierarchy to include a fuller FTN setup for testing. Update testing. --- .env.testing | 4 +- app/Classes/File.php | 32 +- app/Console/Commands/Debug/ZoneCheck.php | 2 +- app/Models/Address.php | 159 +++++--- app/Models/Echomail.php | 2 +- database/seeders/TestNodeHierarchy.php | 254 +++++++------ tests/Feature/PacketTest.php | 10 +- tests/Feature/RoutingTest.php | 456 ++++++++++++++--------- tests/Feature/TicProcessingTest.php | 2 +- 9 files changed, 548 insertions(+), 373 deletions(-) diff --git a/.env.testing b/.env.testing index b156d81..a8b26f6 100644 --- a/.env.testing +++ b/.env.testing @@ -5,6 +5,8 @@ APP_DEBUG=true APP_URL=http://clrghouz APP_TIMEZONE=Australia/Melbourne +CACHE_STORE=array + LOG_CHANNEL=stderr LOG_LEVEL=debug @@ -13,7 +15,7 @@ DB_HOST=postgres-test DB_PORT=5432 DB_DATABASE=test DB_USERNAME=test -DB_PASSWORD=test +DB_PASSWORD=password BROADCAST_DRIVER=log CACHE_DRIVER=file diff --git a/app/Classes/File.php b/app/Classes/File.php index 8df49ce..a5138f7 100644 --- a/app/Classes/File.php +++ b/app/Classes/File.php @@ -22,24 +22,28 @@ class File extends FileBase implements \Iterator { parent::__construct($path,$checkPath); - switch($x=$this->guessExtension()) { - case 'zip': - $this->canHandle = TRUE; - $this->isArchive = TRUE; - $this->z = new \ZipArchive; - $this->z->open($this->getRealPath()); - break; + if ($this->getExtension() === 'pkt') + $this->canHandle = TRUE; - case NULL: - case 'bin': - if ($this->isPacket() || ($path instanceof UploadedFile && (strcasecmp($path->getClientOriginalExtension(),'pkt') === 0))) { + else + switch ($x=$this->guessExtension()) { + case 'zip': $this->canHandle = TRUE; + $this->isArchive = TRUE; + $this->z = new \ZipArchive; + $this->z->open($this->getRealPath()); break; - } - default: - Log::alert(sprintf('%s:? Unknown file received: %s (%s) [%s]',self::LOGKEY,$x,$this->getExtension(),$path instanceof UploadedFile ? $path->getClientOriginalExtension() : '-')); - } + case NULL: + case 'bin': + if ($this->isPacket() || ($path instanceof UploadedFile && (strcasecmp($path->getClientOriginalExtension(),'pkt') === 0))) { + $this->canHandle = TRUE; + break; + } + + default: + Log::alert(sprintf('%s:? Unknown file received: %s (%s) [%s]',self::LOGKEY,$x,$this->getExtension(),$path instanceof UploadedFile ? $path->getClientOriginalExtension() : '-')); + } } /* ITERATOR */ diff --git a/app/Console/Commands/Debug/ZoneCheck.php b/app/Console/Commands/Debug/ZoneCheck.php index cd8cdf6..9e4acbf 100644 --- a/app/Console/Commands/Debug/ZoneCheck.php +++ b/app/Console/Commands/Debug/ZoneCheck.php @@ -25,7 +25,7 @@ class ZoneCheck extends Command $this->warn('Zone: '.$zo->zone_id); $this->info(sprintf('- Our address(es): %s',our_address($do)->pluck('ftn4d')->join(','))); - $this->table(['id','ftn','role','parent','children','downlinks','uplink','send from','region_id','system','notes'],$zo->addresses()->FTNorder()->active()->with(['system'])->dontCache()->get()->transform(function($item) { + $this->table(['id','ftn','role','parent','children','downlinks','uplink','send from','region_id','system','notes'],$zo->addresses()->FTNorder()->active()->with(['system'])->get()->transform(function($item) { return [ 'id'=>$item->id, 'ftn'=>$item->ftn4d, diff --git a/app/Models/Address.php b/app/Models/Address.php index 8981c24..d3eb732 100644 --- a/app/Models/Address.php +++ b/app/Models/Address.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Log; use App\Classes\FTN\{Message,Packet}; use App\Exceptions\InvalidFTNException; -use App\Traits\{QueryCacheableConfig,ScopeActive}; +use App\Traits\ScopeActive; /** * This represents an FTN AKA. @@ -48,7 +48,7 @@ use App\Traits\{QueryCacheableConfig,ScopeActive}; class Address extends Model { - use QueryCacheableConfig,ScopeActive,SoftDeletes; + use ScopeActive,SoftDeletes; private const LOGKEY = 'MA-'; @@ -359,8 +359,8 @@ class Address extends Model // We can only work out region/zone if we have a domain - this is for 2D parsing if ($matches[5] ?? NULL) { $o = new self; - $o->host_id = $matches[2]; - $o->node_id = $matches[3]; + $o->host_id = (int)$matches[2]; + $o->node_id = (int)$matches[3]; $o->point_id = empty($matches[4]) ? 0 : (int)$matches[4]; if ($matches[1] !== "0") { @@ -469,8 +469,7 @@ class Address extends Model ->whereNotNull('export_at') ->whereNull('sent_at') ->whereNull('echomails.deleted_at') - ->groupBy('addresses.id') - ->dontCache(); + ->groupBy('addresses.id'); } public function scopeUncollectedEchomailTotal($query) @@ -504,8 +503,7 @@ class Address extends Model ->whereNotNull('export_at') ->whereNull('sent_at') ->whereNull('files.deleted_at') - ->groupBy('addresses.id') - ->dontCache(); + ->groupBy('addresses.id'); } public function scopeUncollectedFilesTotal($query) @@ -537,8 +535,7 @@ class Address extends Model ->whereNull('sent_pkt') ->whereNull('sent_at') ->whereNull('netmails.deleted_at') - ->groupBy('addresses.id') - ->dontCache(); + ->groupBy('addresses.id'); } /** @@ -639,7 +636,7 @@ class Address extends Model public function nodes_hub(): HasMany { return $this->hasMany(Address::class,'hub_id','id') - ->select(['id','addresses.zone_id','host_id','node_id','point_id','system_id']) + ->select(['id','addresses.zone_id','region_id','host_id','node_id','point_id','system_id']) ->active() ->FTNorder() ->with([ @@ -810,7 +807,7 @@ class Address extends Model public function getIsHostedAttribute(): bool { - return strlen($this->getPassSessionAttribute()) > 0; + return strlen($this->getPassSessionAttribute() ?: '') > 0; } public function getIsHoldAttribute(): bool @@ -943,60 +940,96 @@ class Address extends Model } /** - * Find the immediate children dependent on this record + * This is the children of this record, as per normal FTN routing ZC -> RC -> NC -> HUB -> Node -> Point + * + * This a ZC would return all records for the Zone, + * An RC would only return records in the region, etc * * @return Collection + * @see self::parent() */ public function children(): Collection { // If we are a point, our parent is the boss switch ($this->role_id) { - case self::NODE_NN: // Normal Nodes -> Points - return $this->nodes_point; + case self::NODE_NN: // Normal Nodes + $o = self::active() + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->host_id) + ->where('node_id',$this->node_id); - case self::NODE_HC: // Hubs -> Normal Nodes - return $this->nodes_hub; + break; - case self::NODE_NC: // Nets -> Normal Nodes, excluding Hub's Nodes - return $this->nodes_net->diff($this - ->nodes_net - ->filter(function($item) { return $item->role_id === Address::NODE_HC; }) - ->transform(function($item) { return $item->children(); }) - ->flatten()); + case self::NODE_HC: // Hubs + $o = self::active() + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->host_id) + ->where('hub_id',$this->id) + ->where('id','<>',$this->id) + ->get(); - case self::NODE_RC: // Regions, excluding NC's Nodes - return $this->nodes_region->diff($this - ->nodes_region - ->filter(function($item) { return $item->role_id === Address::NODE_NC; }) - ->transform(function($item) { return $item->nodes_net; }) - ->flatten()); + // Need to add in points of this hub's nodes + return $o->merge( + self::active() + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->host_id) + ->whereIn('node_id',$o->pluck('node_id')) + ->where('point_id','<>',0) + ->get() + ); - case self::NODE_ZC: // Zones, excluding RC's Nodes - return $this->nodes_zone->diff($this - ->nodes_zone - ->filter(function($item) { return $item->role_id === Address::NODE_RC; }) - ->transform(function($item) { return $item->nodes_region; }) - ->flatten()); + case self::NODE_NC: // Nets + $o = self::active() + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) + ->where('host_id',$this->host_id); + + break; + + case self::NODE_RC: // Regions + $o = self::active() + ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id); + + break; + + case self::NODE_ZC: // Zone + $o = self::active() + ->where('zone_id',$this->zone_id); + + break; + + default: + return new Collection; } - return new Collection; + return $o + ->where('id','<>',$this->id) + ->get(); } /** - * Find who we should forward mail onto, taking into account session details that we have + * Contrast to children(), this takes into account authentication details, and we route mail to this + * address (because we have session details) and it's children. * * @return Collection * @throws \Exception + * @see self::children() + * @see self::uplink() */ public function downlinks(): Collection { - // We have no session data for this address, by definition it has no children + // We have no session data for this address, (and its not our address), by definition it has no children if (! $this->is_hosted && (! our_address()->pluck('id')->contains($this->id))) return new Collection; // If this system is not marked to default route for this address if (! $this->is_default_route) { - $children = $this->children(); + $children = $this->children() + ->push($this); // We route everything for this domain } else { @@ -1016,15 +1049,17 @@ class Address extends Model // Exclude links and their children. $exclude = collect(); - foreach (our_nodes($this->zone->domain)->merge(our_address($this->zone->domain)) as $o) { + foreach (our_nodes($this->zone->domain)->diff([$this]) as $o) { + // We only exclude downlink children + if ($o->role_id < $this->role_id) + continue; + // If this address is in our list, remove it and it's children - if ($children->contains($o)) { - $exclude = $exclude->merge($o->children()); - $exclude->push($o); - } + $exclude = $exclude->merge($o->children()); + $exclude->push($o); } - return $children->filter(function($item) use ($exclude) { return ! $exclude->pluck('id')->contains($item->id);}); + return $children->diff($exclude); } /** @@ -1032,6 +1067,7 @@ class Address extends Model * * @return \Illuminate\Support\Collection * @throws \Exception + * @deprecated use children() */ public function downstream(): \Illuminate\Support\Collection { @@ -1073,11 +1109,10 @@ class Address extends Model 'origin:id,value', 'echoarea:id,name,domain_id', 'echoarea.domain:id,name', - 'fftn:id,zone_id,host_id,node_id,point_id', + 'fftn:id,zone_id,region_id,host_id,node_id,point_id', 'fftn.zone:id,domain_id,zone_id', 'fftn.zone.domain:id,name', - ]) - ->dontCache(); + ]); } /** @@ -1097,7 +1132,7 @@ class Address extends Model ->with([ 'filearea:id,name,domain_id', 'filearea.domain:id,name', - 'fftn:id,zone_id,host_id,node_id,point_id', + 'fftn:id,zone_id,region_id,host_id,node_id,point_id', 'fftn.zone:id,domain_id,zone_id', 'fftn.zone.domain:id,name', ]) @@ -1137,7 +1172,7 @@ class Address extends Model $role = self::NODE_ZC; } - if (is_null($role)) + if (isset($this->region_id) && is_null($role)) Log::alert(sprintf('%s:! Address ROLE [%d] could not be determined for [%s]',self::LOGKEY,($this->role & Address::NODE_ALL),$this->ftn)); return $role; @@ -1291,18 +1326,20 @@ class Address extends Model } /** - * Find the immediate parent for this node. + * Find the immediate parent for this address, as per normal FTN routing ZC <- RC <- NC <- HUB <- Node <- Point * * @return Address|null * @throws \Exception + * @see self::children() */ public function parent(): ?Address { // If we are a point, our parent is the boss switch ($this->role_id) { case self::NODE_POINT: // BOSS Node - return Address::active() + return self::active() ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) ->where('host_id',$this->host_id) ->where('node_id',$this->node_id) ->where('point_id',0) @@ -1314,16 +1351,17 @@ class Address extends Model // Else fall through - case self::NODE_HC: // RC - return Address::active() + case self::NODE_HC: // NC + return self::active() ->where('zone_id',$this->zone_id) + ->where('region_id',$this->region_id) ->where('host_id',$this->host_id) ->where('node_id',0) ->where('point_id',0) ->single(); case self::NODE_NC: // RC - return Address::active() + return self::active() ->where('zone_id',$this->zone_id) ->where('region_id',$this->region_id) ->where('host_id',$this->region_id) @@ -1332,7 +1370,7 @@ class Address extends Model ->single(); case self::NODE_RC: // ZC - return Address::active() + return self::active() ->where('zone_id',$this->zone_id) ->where('region_id',0) ->where('node_id',0) @@ -1356,10 +1394,13 @@ class Address extends Model } /** - * Get the appropriate parent for this address, taking into account who we have session information with + * Contrast to parent(), this takes into account authentication details, and we route mail to this + * address (because we have session details) with that uplink. * * @return Address|$this|null * @throws \Exception + * @see self::parent() + * @see self::downlinks() */ public function uplink(): ?Address { @@ -1371,15 +1412,17 @@ class Address extends Model if ($this->is_hosted) return $this; + // Traverse up our parents until we have one with session details if ($x=$this->parent()?->uplink()) { return $x; + // See if we have a node registered as the default route for this zone } else { $sz = SystemZone::whereIn('zone_id',$this->domain->zones->pluck('id')) ->where('default',TRUE) ->single(); - return $sz?->system->addresses->sortBy('security')->last(); + return $sz?->system->akas->sortBy('security')->last(); } } } \ No newline at end of file diff --git a/app/Models/Echomail.php b/app/Models/Echomail.php index 58eb70c..edff859 100644 --- a/app/Models/Echomail.php +++ b/app/Models/Echomail.php @@ -289,7 +289,7 @@ final class Echomail extends Model implements Packet public function seenby() { return $this->belongsToMany(Address::class,'echomail_seenby') - ->select(['id','zone_id','host_id','node_id']) + ->select(['addresses.id','zone_id','host_id','node_id']) ->withPivot(['export_at','sent_at','sent_pkt']) ->dontCache() ->FTN2DOrder(); diff --git a/database/seeders/TestNodeHierarchy.php b/database/seeders/TestNodeHierarchy.php index aee96bc..a2ff5d4 100644 --- a/database/seeders/TestNodeHierarchy.php +++ b/database/seeders/TestNodeHierarchy.php @@ -2,42 +2,39 @@ namespace Database\Seeders; +/** + * Our testing heirarchy. + * + */ + use Carbon\Carbon; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; -use App\Models\{Address, Domain, Setup, System, Zone}; +use App\Models\{Address,Domain,Setup,System,Zone}; class TestNodeHierarchy extends Seeder { - public const DEBUG=FALSE; + public const DEBUG = TRUE; - /** - * Run the database seeds. - * - * @return void - */ + /** + * Run the database seeds. + * + * @return void + * @throws \Exception + */ public function run() { - DB::table('domains') - ->insert([ - 'name'=>'a', - 'active'=>TRUE, - 'public'=>TRUE, - 'created_at'=>Carbon::now(), - 'updated_at'=>Carbon::now(), - ]); + foreach (['a','b','c','d','e','f'] as $domain) { + DB::table('domains') + ->insert([ + 'name'=>$domain, + 'active'=>TRUE, + 'public'=>TRUE, + 'created_at'=>Carbon::now(), + 'updated_at'=>Carbon::now(), + ]); - DB::table('domains') - ->insert([ - 'name'=>'b', - 'active'=>TRUE, - 'public'=>TRUE, - 'created_at'=>Carbon::now(), - 'updated_at'=>Carbon::now(), - ]); - - foreach (['a','b'] as $domain) { $do = Domain::where('name',$domain)->sole(); $this->hierarchy($do,100); $this->hierarchy($do,101); @@ -45,9 +42,38 @@ class TestNodeHierarchy extends Seeder // Configure my addresses $so = Setup::findOrFail(config('app.id')); + + // ZC 100:0/0@a $ao = Address::findFTN('100:0/0@a'); - $so->system_id = $ao->system_id; - $so->save(); + $ao->system_id = $so->system_id; + $ao->save(); + + // RC 100:1/0@b + $ao = Address::findFTN('100:1/0@b'); + $ao->system_id = $so->system_id; + $ao->save(); + + // NC 100:20/0@c + $ao = Address::findFTN('100:20/0@c'); + $ao->system_id = $so->system_id; + $ao->save(); + + // HUB 100:30/100@d + $ao = Address::findFTN('100:30/100@d'); + $ao->system_id = $so->system_id; + $ao->save(); + + // NODE 100:40/101@e + $ao = Address::findFTN('100:40/101@e'); + $ao->system_id = $so->system_id; + $ao->save(); + + // POINT 100:50/101.3277@f + $ao = Address::createFTN('100:50/101.3277@f',$so->system); + + $so->system->name = 'Clearing Houz TEST'; + $so->system->address = 'localhost'; + $so->system->save(); // Add file area DB::table('fileareas') @@ -66,9 +92,7 @@ class TestNodeHierarchy extends Seeder private function hierarchy(Domain $domain,int $zoneid) { - $hosts = [1,2,3,4,5]; - $hubs = [10,20,30,40,50]; - $nodes = [100,200,300,400,500]; + $levels = [1,2,3,4,5]; $hubnodes = [-2,-1,+1,+2,+3]; $so = $this->system(sprintf('ZC %s-%s',$domain->name,$zoneid)); @@ -85,185 +109,196 @@ class TestNodeHierarchy extends Seeder ]); $zo = Zone::where('zone_id',$zoneid)->where('domain_id',$domain->id)->sole(); - if (self::DEBUG) - dump(['zo'=>$zo->zone_id,'rid'=>0,'hid'=>0,'nid'=>0]); - // ZC + if (self::DEBUG) + printf("- ZC %d:%d/%d.%d@%s\n",$zo->zone_id,0,0,0,$domain->name); + + $region_id = 0; + // ZC Address DB::table('addresses') ->insert([ 'zone_id'=>$zo->id, 'active'=>TRUE, - 'validated'=>TRUE, - 'region_id'=>0, - 'host_id'=>0, + 'region_id'=>$region_id, + 'host_id'=>$region_id, 'node_id'=>0, 'point_id'=>0, 'system_id'=>$so->id, - 'role'=>Address::NODE_ZC, 'created_at'=>Carbon::now(), 'updated_at'=>Carbon::now(), ]); // ZC Nodes - foreach ($nodes as $nid) { - if (self::DEBUG) - dump(['rid'=>$zo->zone_id,'hid'=>$zo->zone_id,'nid'=>$nid]); + foreach ($hubnodes as $hnid) { + $host_id = $region_id*100+$hnid+3; - $so = $this->system(sprintf('ZC Node 0/%d',$nid)); + if (self::DEBUG) + printf(" - NODE %d:%d/%d.%d@%s\n",$zo->zone_id,$region_id,$host_id,0,$domain->name); + + $so = $this->system(sprintf('ZC Node 0/%d',$host_id)); DB::table('addresses') ->insert([ 'zone_id'=>$zo->id, 'active'=>TRUE, - 'validated'=>TRUE, - 'region_id'=>0, - 'host_id'=>0, - 'node_id'=>$nid, + 'region_id'=>$region_id, + 'host_id'=>$region_id, + 'node_id'=>$host_id, 'point_id'=>0, 'system_id'=>$so->id, - 'role'=>Address::NODE_NN, 'created_at'=>Carbon::now(), 'updated_at'=>Carbon::now(), ]); } - if (self::DEBUG) - dump(['end'=>'nodes top']); - - // Regions - foreach ($hosts as $rid) { - $hostid = $rid; + // RC + foreach ($levels as $region_id) { if (self::DEBUG) - dump(['rid'=>$rid,'hid'=>$hostid,'nid'=>0]); + printf(" - RC %d:%d/%d.%d@%s\n",$zo->zone_id,$region_id,0,0,$domain->name); - $so = $this->system(sprintf('Region %03d:%03d/%03d.0@%s',$zoneid,$rid,0,$domain->name)); + $so = $this->system(sprintf('RC %03d:%03d/%03d.0@%s',$zoneid,$region_id,0,$domain->name)); DB::table('addresses') ->insert([ 'zone_id'=>$zo->id, 'active'=>TRUE, - 'validated'=>TRUE, - 'region_id'=>$rid, - 'host_id'=>$rid, + 'region_id'=>$region_id, + 'host_id'=>$region_id, 'node_id'=>0, 'point_id'=>0, 'system_id'=>$so->id, - 'role'=>Address::NODE_RC, 'created_at'=>Carbon::now(), 'updated_at'=>Carbon::now(), ]); // RC Nodes - foreach ($nodes as $nid) { - if (self::DEBUG) - dump(['rid'=>$rid,'hid'=>$hostid,'nid'=>$nid]); + foreach ($hubnodes as $hnid) { + $host_id = $hnid+3; - $so = $this->system(sprintf('RC Node %d/%d',$rid,$nid)); + if (self::DEBUG) + printf(" - NODE %d:%d/%d.%d@%s\n",$zo->zone_id,$region_id,$host_id,0,$domain->name); + + $so = $this->system(sprintf('RC Node %d/%d',$region_id,$host_id)); DB::table('addresses') ->insert([ 'zone_id'=>$zo->id, 'active'=>TRUE, - 'validated'=>TRUE, - 'region_id'=>$rid, - 'host_id'=>$rid, - 'node_id'=>$nid, + 'region_id'=>$region_id, + 'host_id'=>$region_id, + 'node_id'=>$host_id, 'point_id'=>0, 'system_id'=>$so->id, - 'role'=>Address::NODE_NN, 'created_at'=>Carbon::now(), 'updated_at'=>Carbon::now(), ]); } - if (self::DEBUG) - dump(['end'=>'NODES regions']); - // Hosts - foreach ($hosts as $rrid) { - $hostid = $rid*10+$rrid-1; + // NC + foreach ($levels as $ncid) { + $net_id = $region_id*10+$ncid-1; if (self::DEBUG) - dump(['rid'=>$rid,'hid'=>$hostid,'nid'=>0]); - - $so = $this->system(sprintf('Host %d:%d/0 (R%d)',$zoneid,$hostid,$rid)); + printf(" - NC %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,0,0,$domain->name); + $so = $this->system(sprintf('NC %d:%d/0 (R%d)',$zo->zone_id,$net_id,$region_id)); DB::table('addresses') ->insert([ 'zone_id'=>$zo->id, 'active'=>TRUE, - 'validated'=>TRUE, - 'region_id'=>$rid, - 'host_id'=>$hostid, + 'region_id'=>$region_id, + 'host_id'=>$net_id, 'node_id'=>0, 'point_id'=>0, 'system_id'=>$so->id, - 'role'=>Address::NODE_NC, 'created_at'=>Carbon::now(), 'updated_at'=>Carbon::now(), ]); - // Nodes - foreach ($nodes as $nid) { - if (self::DEBUG) - dump(['rid'=>$rid,'hid'=>$hostid,'nid'=>$nid]); + // NC Nodes + foreach ($hubnodes as $hnid) { + $host_id = $hnid+3; - $so = $this->system(sprintf('Host Node %d/%d (R%d)',$hostid,$nid,$rid)); + if (self::DEBUG) + printf(" - NODE %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,$host_id,0,$domain->name); + + $so = $this->system(sprintf('NC Node %d/%d (R%d)',$net_id,$host_id,$region_id)); DB::table('addresses') ->insert([ 'zone_id'=>$zo->id, 'active'=>TRUE, - 'validated'=>TRUE, - 'region_id'=>$rid, - 'host_id'=>$hostid, - 'node_id'=>$nid, + 'region_id'=>$region_id, + 'host_id'=>$net_id, + 'node_id'=>$host_id, 'point_id'=>0, 'system_id'=>$so->id, - 'role'=>Address::NODE_NN, 'created_at'=>Carbon::now(), 'updated_at'=>Carbon::now(), ]); } // Hubs - foreach ($hubs as $bid) { - $so = $this->system(sprintf('HUB %d/%d (R%d)',$hostid,$bid,$rid)); + foreach ($levels as $hbid) { + $host_id = $hbid*100; + + if (self::DEBUG) + printf(" - HUB %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,$host_id,0,$domain->name); + + $so = $this->system(sprintf('HUB %d/%d (R%d)',$net_id,$host_id,$region_id)); $hub = new Address; $hub->zone_id = $zo->id; $hub->active = TRUE; - $hub->region_id = $rid; - $hub->host_id = $hostid; - $hub->node_id = $bid; + $hub->region_id = $region_id; + $hub->host_id = $net_id; + $hub->node_id = $host_id; $hub->point_id = 0; $hub->system_id = $so->id; - $hub->role = Address::NODE_HC; $hub->created_at = Carbon::now(); $hub->updated_at = Carbon::now(); $hub->save(); - // Nodes + // HUB Nodes foreach ($hubnodes as $nid) { - $nodeid = $bid+$nid; - $so = $this->system(sprintf('Hub Node %d/%d (R%d/H%d)',$hostid,$nodeid,$rid,$hub->node_id)); + $host_id = $hub->node_id+$nid; + + if (self::DEBUG) + printf(" - NODE %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,$host_id,0,$domain->name); + + $so = $this->system(sprintf('Hub Node %d/%d (R%d/H%d)',$net_id,$host_id,$region_id,$hub->node_id)); DB::table('addresses') ->insert([ 'zone_id'=>$zo->id, 'active'=>TRUE, - 'validated'=>TRUE, - 'region_id'=>$rid, - 'host_id'=>$hostid, - 'node_id'=>$nodeid, + 'region_id'=>$region_id, + 'host_id'=>$net_id, + 'node_id'=>$host_id, 'point_id'=>0, 'system_id'=>$so->id, 'hub_id'=>$hub->id, - 'role'=>Address::NODE_NN, 'created_at'=>Carbon::now(), 'updated_at'=>Carbon::now(), ]); + + foreach ($hubnodes as $nid) { + $point_id = $nid+3; + + if (self::DEBUG) + printf(" - POINT %d:%d/%d.%d@%s\n",$zo->zone_id,$net_id,$host_id,$point_id,$domain->name); + + $so = $this->system(sprintf('Node Point %d/%d.%d (R%d/H%d)',$net_id,$host_id,$point_id,$region_id,$hub->node_id)); + DB::table('addresses') + ->insert([ + 'zone_id'=>$zo->id, + 'active'=>TRUE, + 'region_id'=>$region_id, + 'host_id'=>$net_id, + 'node_id'=>$host_id, + 'point_id'=>$point_id, + 'system_id'=>$so->id, + 'created_at'=>Carbon::now(), + 'updated_at'=>Carbon::now(), + ]); + } } } } - if (self::DEBUG) - dump(['end'=>'NODES normal']); } - - if (self::DEBUG) - dump(['end'=>'heirarchy']); } private function system(string $name): System @@ -275,8 +310,9 @@ class TestNodeHierarchy extends Seeder $o->active = TRUE; $o->created_at = Carbon::now(); $o->updated_at = Carbon::now(); + $o->address = 'myhostname'; $o->save(); return $o; } -} +} \ No newline at end of file diff --git a/tests/Feature/PacketTest.php b/tests/Feature/PacketTest.php index d8e3adf..85c899d 100644 --- a/tests/Feature/PacketTest.php +++ b/tests/Feature/PacketTest.php @@ -109,7 +109,7 @@ class PacketTest extends TestCase $f = $this->mail_file('mail/018A-1702393055-78754407.pkt'); foreach ($f as $packet) { - $pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$this->do); + $pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize()); $this->assertInstanceOf(Packet\FSC39::class,$pkt); $this->assertEquals('ABCDEFGH',$pkt->password); @@ -136,7 +136,7 @@ class PacketTest extends TestCase $f = $this->mail_file('mail/018A-1701955391-71c7a400.pkt'); foreach ($f as $packet) { - $pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$this->do); + $pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize()); $this->assertInstanceOf(Packet\FSC39::class,$pkt); $this->assertEquals('ABCDEFGH',$pkt->password); @@ -163,7 +163,7 @@ class PacketTest extends TestCase $f = $this->mail_file('mail/0B39-1701919239-65713a06.pkt'); foreach ($f as $packet) { - $pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize(),$this->do); + $pkt = Packet::process($packet,$x=$f->itemName(),$f->itemSize()); $this->assertInstanceOf(Packet\FSC48::class,$pkt); $this->assertEquals('ABCDEFG#',$pkt->password); @@ -246,7 +246,7 @@ class PacketTest extends TestCase // This packet has an incorrect zone in the Origin $f = $this->mail_file('mail/test_msgid_origin.pkt'); foreach ($f as $packet) { - $pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->do); + $pkt = Packet::process($packet,$f->itemName(),$f->itemSize()); $this->assertInstanceOf(Packet\FSC39::class,$pkt); $this->assertEquals(1,$pkt->count()); @@ -278,7 +278,7 @@ class PacketTest extends TestCase // This packet has a SOHSOH sequence $f = $this->mail_file('mail/test_binary_content-2.pkt'); foreach ($f as $packet) { - $pkt = Packet::process($packet,$f->itemName(),$f->itemSize(),$this->do); + $pkt = Packet::process($packet,$f->itemName(),$f->itemSize()); $this->assertEquals(1,$pkt->count()); diff --git a/tests/Feature/RoutingTest.php b/tests/Feature/RoutingTest.php index 214f654..80fd7e4 100644 --- a/tests/Feature/RoutingTest.php +++ b/tests/Feature/RoutingTest.php @@ -5,6 +5,7 @@ namespace Tests\Feature; use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; use Tests\TestCase; use App\Models\{Address,Domain,Setup,System}; @@ -53,12 +54,17 @@ class RoutingTest extends TestCase { use DatabaseTransactions; + public const ZONE_ADDRS = 4061; + public const REGION_ADDRS = 811; + public const NET_ADDRS = 161; + public const HUB_ADDRS = 31; + public const NODE_ADDRS = 6; + private function zone(): Collection { - //$this->seed(TestNodeHierarchy::class); - $do = Domain::where('name','a')->sole(); $zo = $do->zones->where('zone_id',100)->pop(); + return $zo->addresses; } @@ -70,19 +76,31 @@ class RoutingTest extends TestCase private function session_rc(): void { - $ao = Address::findFTN('100:1/0@a'); + $ao = Address::findFTN('101:1/0@a'); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); } private function session_nc(): void { - $ao = Address::findFTN('100:10/0@a'); + $ao = Address::findFTN('101:10/0@a'); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); } private function session_hub(): void { - $ao = Address::findFTN('100:10/20@a'); + $ao = Address::findFTN('101:10/100@a'); + $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); + } + + private function session_node(): void + { + $ao = Address::findFTN('101:10/101@a'); + $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); + } + + private function session_point(): void + { + $ao = Address::findFTN('101:10/101.1@a'); $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); } @@ -90,14 +108,14 @@ class RoutingTest extends TestCase public function test_my_addresses() { $ftns = our_address(); - $this->assertEquals(1,$ftns->count()); + $this->assertEquals(6,$ftns->count()); } // Get a list of active addresses in a Zone - public function test_zc_addresses() + public function test_zone_addresses() { $nodes = $this->zone(); - $this->assertEquals(936,$nodes->count()); + $this->assertEquals(self::ZONE_ADDRS,$nodes->count()); } // Get a ZC with no session details, and make sure it has no parent nor children @@ -107,230 +125,302 @@ class RoutingTest extends TestCase $ao = Address::findFTN('101:0/0@a'); $this->assertEquals($ao->role_id,Address::NODE_ZC); - $this->assertCount(0,$ao->downstream()); + $this->assertCount(0,$ao->downlinks()); $this->assertNull($ao->uplink()); } // If we have a ZC, make sure we are routing to all it's children public function test_zc_session_children() { - $this->session_zc(); - $ao = Address::findFTN('101:0/0@a'); - $this->assertCount(935,$ao->downstream()); - } - // An RC's parent should be the ZC, when we have session details with parent - public function test_zc_rc_node_parent() - { + // This is a ZC + $this->assertEquals($ao->role_name,'ZC'); + $this->assertEquals(Address::NODE_ZC,$ao->role_id); + + // Children + $this->assertCount(self::ZONE_ADDRS-1,$ao->children()); + $this->assertCount(0,$ao->downlinks()); + $this->session_zc(); + $ao->refresh(); - $ao = Address::findFTN('101:1/0@a'); - $this->assertEquals($ao->role_id,Address::NODE_RC); - $this->assertEquals('101:0/0.0@a',$ao->uplink()->ftn); + // Children + $this->assertCount(self::ZONE_ADDRS,$ao->downlinks()); + } + + // ZC uplink should be null, and the parent should be itself + public function test_zc_parent() + { + $ao = Address::findFTN('101:0/0@a'); + + // This uplink should also be null + $this->assertNull($ao->uplink()?->ftn); + + $this->session_zc(); + $ao->refresh(); + + // This parent should also be null + $this->assertNull($ao->parent()?->ftn); + + // This uplink should be itself + $this->assertEquals($ao->ftn,$ao->uplink()?->ftn); } - // Get a list of active addresses in a Region public function test_rc_session_children() { - $this->session_rc(); + $ao = Address::findFTN('101:1/0@a'); - $ao = Address::findFTN('100:1/0@a'); - $this->assertCount(185,$ao->downstream()); + // This is a RC + $this->assertEquals($ao->role_name,'RC'); + $this->assertEquals(Address::NODE_RC,$ao->role_id); + + // Children + $this->assertCount(self::REGION_ADDRS-1,$ao->children()); + $this->assertCount(0,$ao->downlinks()); + + $this->session_rc(); + $ao->refresh(); + + // This is a ZC + $this->assertCount(self::REGION_ADDRS,$ao->downlinks()); } - // An RCs node still collects mail from the RC - public function test_rc_node_rc() - { - $this->session_rc(); - - // An RCs node should still be the RC - $ao = Address::findFTN('100:1/100@a'); - $this->assertEquals($ao->role_id,Address::NODE_NN); - $this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn); - } - - // An NC collects mail for its children - public function test_rc_nc_node_rc() - { - $this->session_rc(); - - // A NCs parent should still be the RC - $ao = Address::findFTN('100:10/0@a'); - $this->assertEquals($ao->role_id,Address::NODE_NC); - $this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn); - } - - // A Hub still collects mail from NC - public function test_rc_hub_node_nc() - { - $this->session_rc(); - - // A Hubs parent should still be the NC - $ao = Address::findFTN('100:10/20.0@a'); - $this->assertEquals($ao->role_id,Address::NODE_HC); - $this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn); - } - - // A Hub's node still collects mail from Hub - public function test_rc_hub_node_hub() - { - $this->session_rc(); - - // A Hubs node should still be the Hub - $ao = Address::findFTN('100:10/22.0@a'); - $this->assertEquals($ao->role_id,Address::NODE_NN); - $this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn); - } - - // An RCs parent is us even if we have session details for another RC public function test_rc_parent() { - $this->session_rc(); + $ao = Address::findFTN('101:1/0@a'); + $o = Address::findFTN('101:0/0@a'); - $ao = Address::findFTN('100:2/0@a'); + // This uplink should also be null $this->assertNull($ao->uplink()?->ftn); - } - // If we also have session details for an NC, then there are less RC nodes - public function test_rc_nc_session_children() - { $this->session_rc(); - $this->session_nc(); + $ao->refresh(); - $ao = Address::findFTN('100:1/0@a'); - $this->assertCount(185-36,$ao->downstream()); + // This parent should also be null + $this->assertEquals($o->ftn,$ao->parent()?->ftn); + + // This uplink should be itself + $this->assertEquals($ao->ftn,$ao->uplink()?->ftn); } - // If we also have session details for an Hub, then there are less RC nodes - public function test_rc_hub_session_children() + public function test_zc_rc_nodes() { + $this->session_zc(); $this->session_rc(); - $ao = Address::findFTN('100:10/20@a'); - $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); + $ao = Address::findFTN('101:0/0@a'); + $this->assertCount(self::ZONE_ADDRS-self::REGION_ADDRS,$ao->downlinks()); - $ao = Address::findFTN('100:1/0@a'); - $this->assertCount(185,$ao->downstream()); - - $ao = Address::findFTN('100:10/22@a'); - $this->assertEquals('100:10/20.0@a',$ao->uplink()->ftn); + $ao = Address::findFTN('101:1/0@a'); + $this->assertCount(self::REGION_ADDRS,$ao->downlinks()); } - // If we also have session details for an Hub, then there are less RC nodes - public function test_rc_hub_session_child() - { - $this->session_hub(); - - $ao = Address::findFTN('100:10/22@a'); - $this->assertEquals('100:10/20.0@a',$ao->uplink()->ftn); - } - - // When we have an RC with session details, we route to all its children public function test_nc_session_children() { - $this->session_nc(); + $ao = Address::findFTN('101:10/0@a'); - $ao = Address::findFTN('100:10/0@a'); - $this->assertCount(35,$ao->downstream()); + // This is a NC + $this->assertEquals($ao->role_name,'NC'); + $this->assertEquals(Address::NODE_NC,$ao->role_id); + + // Children + $this->assertCount(self::NET_ADDRS-1,$ao->children()); + $this->assertCount(0,$ao->downlinks()); + + $this->session_nc(); + $ao->refresh(); + + // This is a NC + $this->assertCount(self::NET_ADDRS,$ao->downlinks()); } - public function test_complex_rc_nc_hc() + public function test_nc_parent() { - $this->session_rc(); - $this->session_nc(); - $this->session_hub(); + $ao = Address::findFTN('101:10/0@a'); + $o = Address::findFTN('101:1/0@a'); - $ao = Address::findFTN('100:1/100.0@a'); - $this->assertCount(0,$ao->downstream()); - $this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn); - - // RC - $ao = Address::findFTN('100:1/0.0@a'); - $this->assertCount(186-1-30-6,$ao->downstream()); - - $ao = Address::findFTN('100:11/0.0@a'); - $this->assertEquals('100:1/0.0@a',$ao->uplink()->ftn); - - // NC - $ao = Address::findFTN('100:10/0.0@a'); - $this->assertCount(36-1-6,$ao->downstream()); - - $ao = Address::findFTN('100:10/10.0@a'); - $this->assertEquals('100:10/0.0@a',$ao->uplink()->ftn); - - // HC - $ao = Address::findFTN('100:10/20.0@a'); - $this->assertCount(6-1,$ao->downstream()); - - $ao = Address::findFTN('100:10/22.0@a'); - $this->assertEquals('100:10/20.0@a',$ao->uplink()->ftn); - } - - public function test_complex_rc_nc_hc_us() - { - Cache::forget('so'); - $setup = Setup::findOrFail(config('app.id')); - $ao = Address::findFTN('100:10/0.0@a'); - $setup->system_id = $ao->system_id; - $setup->save(); - $this->assertEquals('100:10/0.0@a',our_address($ao)?->ftn); - - $this->session_rc(); - //$this->session_nc(); - $this->session_hub(); - $ao = Address::findFTN('100:11/0.0'); - $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); - - $ao = Address::findFTN('100:1/100.0@a'); - $this->assertCount(0,$ao->downstream()); - $this->assertEquals('100:1/0.0@a',$ao->uplink()?->ftn); - - // RC - $ao = Address::findFTN('100:1/0.0@a'); - $this->assertCount(186-36-36-1,$ao->downstream()); - - $ao = Address::findFTN('100:11/0.0@a'); - $this->assertEquals('100:11/0.0@a',$ao->uplink()->ftn); - - // NC - $ao = Address::findFTN('100:10/0.0@a'); - $this->assertCount(36-6-1,$ao->downstream()); - - $ao = Address::findFTN('100:10/10.0@a'); + // This uplink should also be null $this->assertNull($ao->uplink()?->ftn); - // HC - $ao = Address::findFTN('100:10/20.0@a'); - $this->assertCount(6-1,$ao->downstream()); + $this->session_nc(); + $ao->refresh(); - $ao = Address::findFTN('100:10/22.0@a'); - $this->assertEquals('100:10/20.0@a',$ao->uplink()->ftn); - Cache::forget('so'); + // This parent should also be null + $this->assertEquals($o->ftn,$ao->parent()?->ftn); + + // This uplink should be itself + $this->assertEquals($ao->ftn,$ao->uplink()?->ftn); } - // A points parent is the node, if we have traffic for a point and we have session details for the node - public function test_point_session_node() + public function test_zc_rc_nc_nodes() { - //$this->session_hub(); + $this->session_zc(); + $this->session_rc(); + $this->session_nc(); - $so = new System; - $so->name = 'Point System'; - $so->sysop = 'Point Sysop'; - $so->location = 'Melbourne, AU'; - $so->active = TRUE; - $so->save(); + $ao = Address::findFTN('101:0/0@a'); + $this->assertCount(self::ZONE_ADDRS-self::REGION_ADDRS,$ao->downlinks()); - // Create a child - $ao = Address::createFTN('100:10/21.2@a',$so); + $ao = Address::findFTN('101:1/0@a'); + $this->assertCount(self::REGION_ADDRS-self::NET_ADDRS,$ao->downlinks()); - $ao = Address::findFTN('100:10/21.0@a'); - $ao->system->sessions()->attach([$ao->zone_id=>['sespass'=>'ABCD']]); + $ao = Address::findFTN('101:10/0@a'); + $this->assertCount(self::NET_ADDRS,$ao->downlinks()); + } - $ao = Address::findFTN('100:10/21.2@a'); - $this->assertEquals('100:10/21.0@a',$ao->uplink()?->ftn); + public function test_hub_session_children() + { + $ao = Address::findFTN('101:10/100@a'); - $ao = Address::findFTN('100:10/21@a'); - $this->assertCount(1,$ao->downstream()); + // This is a HUB + $this->assertEquals($ao->role_name,'HUB'); + $this->assertEquals(Address::NODE_HC,$ao->role_id); + + // Children + $this->assertCount(self::HUB_ADDRS-1,$ao->children()); + $this->assertCount(0,$ao->downlinks()); + + $this->session_hub(); + $ao->refresh()->unsetRelations(); + + // This is a NC + $this->assertCount(self::HUB_ADDRS,$ao->downlinks()); + } + + public function test_hub_parent() + { + $ao = Address::findFTN('101:10/100@a'); + $o = Address::findFTN('101:10/0@a'); + + // This uplink should also be null + $this->assertNull($ao->uplink()?->ftn); + + $this->session_hub(); + $ao->refresh(); + + // This parent should also be null + $this->assertEquals($o->ftn,$ao->parent()?->ftn); + + // This uplink should be itself + $this->assertEquals($ao->ftn,$ao->uplink()?->ftn); + } + + public function test_zc_rc_nc_hub_nodes() + { + $this->session_zc(); + $this->session_rc(); + $this->session_nc(); + $this->session_hub(); + + $ao = Address::findFTN('101:0/0@a'); + $this->assertCount(self::ZONE_ADDRS-self::REGION_ADDRS,$ao->downlinks()); + + $ao = Address::findFTN('101:1/0@a'); + $this->assertCount(self::REGION_ADDRS-self::NET_ADDRS,$ao->downlinks()); + + $ao = Address::findFTN('101:10/0@a'); + $this->assertCount(self::NET_ADDRS-self::HUB_ADDRS,$ao->downlinks()); + + $ao = Address::findFTN('101:10/100@a'); + $this->assertCount(self::HUB_ADDRS,$ao->downlinks()); + } + + public function test_node_session_children() + { + $ao = Address::findFTN('101:10/101@a'); + + // This is a NC + $this->assertEquals($ao->role_name,'NODE'); + $this->assertEquals(Address::NODE_NN,$ao->role_id); + + // Children + $this->assertCount(self::NODE_ADDRS-1,$ao->children()); + $this->assertCount(0,$ao->downlinks()); + + $this->session_node(); + $ao->refresh(); + + // This is a NC + $this->assertCount(self::NODE_ADDRS,$ao->downlinks()); + } + + public function test_node_parent() + { + $ao = Address::findFTN('101:10/101@a'); + $o = Address::findFTN('101:10/100@a'); + + // This uplink should also be null + $this->assertNull($ao->uplink()?->ftn); + + $this->session_node(); + $ao->refresh(); + + // This parent should also be null + $this->assertEquals($o->ftn,$ao->parent()?->ftn); + + // This uplink should be itself + $this->assertEquals($ao->ftn,$ao->uplink()?->ftn); + } + + public function test_zc_rc_nc_hub_node_nodes() + { + $this->session_zc(); + $this->session_rc(); + $this->session_nc(); + $this->session_hub(); + $this->session_node(); + + $ao = Address::findFTN('101:0/0@a'); + $this->assertCount(self::ZONE_ADDRS-self::REGION_ADDRS,$ao->downlinks()); + + $ao = Address::findFTN('101:1/0@a'); + $this->assertCount(self::REGION_ADDRS-self::NET_ADDRS,$ao->downlinks()); + + $ao = Address::findFTN('101:10/0@a'); + $this->assertCount(self::NET_ADDRS-self::HUB_ADDRS,$ao->downlinks()); + + $ao = Address::findFTN('101:10/100@a'); + $this->assertCount(self::HUB_ADDRS-self::NODE_ADDRS,$ao->downlinks()); + + $ao = Address::findFTN('101:10/101@a'); + $this->assertCount(self::NODE_ADDRS,$ao->downlinks()); + } + + public function test_point_session_children() + { + $ao = Address::findFTN('101:10/101.1@a'); + + // This is a NC + $this->assertEquals($ao->role_name,'POINT'); + $this->assertEquals(Address::NODE_POINT,$ao->role_id); + + // Children + $this->assertCount(0,$ao->children()); + $this->assertCount(0,$ao->downlinks()); + + $this->session_point(); + $ao->refresh(); + + // This is a NC + $this->assertCount(1,$ao->downlinks()); + } + + public function test_point_parent() + { + $ao = Address::findFTN('101:10/101.1@a'); + $o = Address::findFTN('101:10/101@a'); + + // This uplink should also be null + $this->assertNull($ao->uplink()?->ftn); + + $this->session_point(); + $ao->refresh(); + + // This parent should also be null + $this->assertEquals($o->ftn,$ao->parent()?->ftn); + + // This uplink should be itself + $this->assertEquals($ao->ftn,$ao->uplink()?->ftn); } } \ No newline at end of file diff --git a/tests/Feature/TicProcessingTest.php b/tests/Feature/TicProcessingTest.php index c3c5e11..d0f4c6d 100644 --- a/tests/Feature/TicProcessingTest.php +++ b/tests/Feature/TicProcessingTest.php @@ -131,7 +131,7 @@ class TicProcessingTest extends TestCase $file->refresh(); $this->assertEquals('100:1/0',$file->fftn->ftn3d); $this->assertEquals('100:10/11',$file->origin->ftn3d); - $this->assertCount(12,$file->seenby); + $this->assertCount(7,$file->seenby); $this->assertCount(4,$file->path); } }