Index: trunk/Nebulous-Server/t/03_server_setup.t
===================================================================
--- trunk/Nebulous-Server/t/03_server_setup.t	(revision 24995)
+++ trunk/Nebulous-Server/t/03_server_setup.t	(revision 41172)
@@ -14,4 +14,7 @@
 use Nebulous::Server;
 use Test::Nebulous;
+
+Test::Nebulous->show_setup;
+die "test";
 
 Test::Nebulous->setup;
Index: trunk/Nebulous-Server/t/04_server_create_object.t
===================================================================
--- trunk/Nebulous-Server/t/04_server_create_object.t	(revision 24995)
+++ trunk/Nebulous-Server/t/04_server_create_object.t	(revision 41172)
@@ -30,5 +30,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key
@@ -40,12 +39,11 @@
     uri_scheme_ok($uri, 'file');
 
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
+  SKIP: {
+      skip "requires xattr support", 1 unless $test_xattr;
+      is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+    }
+}
+
+Test::Nebulous->setup;
 {
     # key
@@ -57,8 +55,8 @@
     uri_scheme_ok($uri, 'file');
 
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
+  SKIP: {
+      skip "requires xattr support", 1 unless $test_xattr;
+      is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+    }
 }
 
@@ -528,5 +526,5 @@
     $neb->create_object("foo", '~node07');
 };
-like($@, qr/node07 is not available/, "request volume this is full");
+like($@, qr/node07 is not available/, "request volume which is full");
 
 Test::Nebulous->setup;
@@ -535,5 +533,5 @@
     $neb->create_object("neb://~node07/foo");
 };
-like($@, qr/node07 is not available/, "request volume this is full");
+like($@, qr/node07 is not available/, "request volume which is full");
 
 Test::Nebulous->setup;
Index: trunk/Nebulous-Server/t/05_server_replicate_object.t
===================================================================
--- trunk/Nebulous-Server/t/05_server_replicate_object.t	(revision 24995)
+++ trunk/Nebulous-Server/t/05_server_replicate_object.t	(revision 41172)
@@ -8,5 +8,5 @@
 use warnings FATAL => qw( all );
 
-use Test::More tests => 25;
+use Test::More tests => 43;
 
 use lib qw( ./t ./lib );
@@ -28,5 +28,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key
@@ -45,5 +44,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key, $volume
@@ -62,5 +60,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key, $volume is undef
@@ -79,5 +76,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key, $volume
@@ -147,4 +143,166 @@
 }
 
+# hard_volume tests:
+
+# replication with hard_volume:
+Test::Nebulous->setup;
+{
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # checking on replicate_object: hard_volume as node target
+    my $uri1 = $neb->create_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo", "~node02");
+
+    my $locations = $neb->find_instances("foo");
+    my $Nloc = @$locations;
+
+    is($Nloc, 2, "found 2 instances");
+    
+    my $loc1 = $locations->[0];
+    my $loc2 = $locations->[1];
+
+    like($loc1, qr/$nodedirs[0]/, "instance matches expected node");
+    like($loc2, qr/$nodedirs[1]/, "instance matches expected node");
+}
+
+# replication to unknown volume: should succeed
+Test::Nebulous->setup;
+{
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # checking on replicate_object, does this logic make sense?
+    my $uri1 = $neb->create_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo", "dummy");
+
+    my $locations = $neb->find_instances("foo");
+    my $Nloc = @$locations;
+
+    is($Nloc, 2, "found 2 instances");
+    
+    my $loc1 = $locations->[0];
+    my $loc2 = $locations->[1];
+
+    like($loc1, qr/$nodedirs[0]/, "instance matches expected node");
+    uri_scheme_ok($loc2, 'file');
+}
+
+# replication to unknown volume with hard_volume: should fail
+Test::Nebulous->setup;
+eval {
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # checking on replicate_object, does this logic make sense?
+    my $uri1 = $neb->create_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo", "~dummy");
+
+    my $locations = $neb->find_instances("foo");
+};
+like($@, qr/is not a valid volume name/, "replicate to unknown node with hard_volume should fail");
+
+# replication to unavailable volume: should succeed
+Test::Nebulous->setup;
+{
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # checking on replicate_object, does this logic make sense?
+    my $uri1 = $neb->create_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo", "node04");
+
+    my $locations = $neb->find_instances("foo");
+    my $Nloc = @$locations;
+
+    is($Nloc, 2, "found 2 instances");
+    
+    my $loc1 = $locations->[0];
+    my $loc2 = $locations->[1];
+
+    like($loc1, qr/$nodedirs[0]/, "instance matches expected node");
+    uri_scheme_ok($loc2, 'file');
+}
+
+# replication to unavailable volume with hard_volume: should fail
+Test::Nebulous->setup;
+eval {
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # checking on replicate_object, does this logic make sense?
+    my $uri1 = $neb->create_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo", "~node04");
+
+    my $locations = $neb->find_instances("foo");
+};
+like($@, qr/is not available/, "replicate to unknown node with hard_volume should fail");
+
+### END hard_volume tests
+
+### location-aware tests (do we respect the cab_id / vol_id exclusions?)
+
+# here are the volume definitions (Test/Nebulous.pm):
+# name   : vol_id : cab_id : site_id : xattr
+# node01 :      1 :      1 :       1 :     0
+# node02 :      2 :      2 :       1 :     0
+# node03 :      3 :      3 :       2 :     0
+# node04 :      4 :      1 :       1 :     0
+# node05 :      5 :      1 :       1 :     0
+# node06 :      6 :      1 :       1 :     0
+# node07 :      7 :      1 :       1 :     0
+# node08 :      8 :      4 :       2 :     0
+
+# replication without volume 
+Test::Nebulous->setup;
+{
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # checking on replicate_object: hard_volume as node target
+    my $uri1 = $neb->create_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo");
+
+    my $locations = $neb->find_instances("foo");
+    my $Nloc = @$locations;
+
+    is($Nloc, 2, "found 2 instances");
+    
+    my $loc1 = $locations->[0];
+    my $loc2 = $locations->[1];
+
+    # if the volume is not explicitly specified, then the first copy
+    # should go to a different volume, cabinet, and site
+    # if first is one node01, replication should go to node03 or node08
+    # since those both have different site_id values
+    like($loc1, qr/$nodedirs[0]/, "instance matches expected node");
+
+    my $isnode03 = $loc2 =~ m/$nodedirs[2]/;
+    my $isnode08 = $loc2 =~ m/$nodedirs[7]/;
+    ok($isnode03 || $isnode08, "instance matches expected node");
+}
+
+# replication without volume
+Test::Nebulous->setup;
+{
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # checking on replicate_object, does this logic make sense?
+    my $uri1 = $neb->create_object("foo", "~node03");
+    my $uri2 = $neb->replicate_object("foo");
+
+    my $locations = $neb->find_instances("foo");
+    my $Nloc = @$locations;
+
+    is($Nloc, 2, "found 2 instances");
+    
+    my $loc1 = $locations->[0];
+    my $loc2 = $locations->[1];
+
+    # if the volume is not explicitly specified, then the first copy
+    # should go to a different volume, cabinet, and site
+    # if first is one node03, replication should go to node01 or node02
+    # since those both have different site_id values
+    like($loc1, qr/$nodedirs[2]/, "instance matches expected node");
+
+    my $isnode01 = $loc2 =~ m/$nodedirs[0]/;
+    my $isnode02 = $loc2 =~ m/$nodedirs[1]/;
+    ok($isnode01 || $isnode02, "instance matches expected node");
+}
+
 Test::Nebulous->setup;
 
Index: trunk/Nebulous-Server/t/08_server_find_instances.t
===================================================================
--- trunk/Nebulous-Server/t/08_server_find_instances.t	(revision 24995)
+++ trunk/Nebulous-Server/t/08_server_find_instances.t	(revision 41172)
@@ -8,5 +8,5 @@
 use warnings FATAL => qw( all );
 
-use Test::More tests => 19;
+use Test::More tests => 44;
 
 use lib qw( ./t ./lib );
@@ -22,6 +22,19 @@
 );
 
-Test::Nebulous->setup;
-
+# XXX previously, the logic was: if we request a hard volume location
+# using ~node, then we would give an error if no data is available except on that node.
+
+# the code has been modified so that the requested storage object is provided if possible, 
+# even if the apparently "hard" target did not exist.
+
+Test::Nebulous->setup;
+Test::Nebulous->cleanup; # make sure the database is reset before running any tests
+
+# for (my $i = 0; $i < $Nloc; $i++) {
+#	my $loc = $locations->[$i];
+#	print "location: $loc\n";
+#}
+
+Test::Nebulous->setup;
 {
     # key
@@ -35,5 +48,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key
@@ -49,5 +61,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key, volume
@@ -61,5 +72,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key, volume
@@ -73,5 +83,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key, volume
@@ -85,5 +94,4 @@
 
 Test::Nebulous->setup;
-
 {
     # key
@@ -98,14 +106,158 @@
 }
 
-# object exists but instance is on a different volume
-Test::Nebulous->setup;
-
+# request from a hard_volume returns an instance which is not on the hard volume if available
+Test::Nebulous->setup;
 eval {
     # key, volume
     my $uri = $neb->create_object('foo', '~node01');
-
     my $locations = $neb->find_instances('foo', '~node02');
-};
-like($@, qr/no instances on storage volume/, 'instances on a different volume');
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+};
+is($@, "", "instance on different node (as hard_volume) returned");
+
+# request from a hard_volume returns an instance which is not on the hard volume if available
+Test::Nebulous->setup;
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', '~node01');
+    my $locations = $neb->find_instances('foo', '~node02');
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+};
+is($@, "", "instance on different node (as hard_volume) returned");
+
+### TESTS FOR INSTANCE ON INVALID NODE
+diag ("TESTS FOR INSTANCE ON INVALID NODE");
+
+# request from a soft_volume for an instance which is on an invalid node (find_invalid = 1) [SOFT, 1]
+Test::Nebulous->setup;
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', '~node02');
+    Test::Nebulous->switch_node_state;
+
+    my $locations = $neb->find_instances('foo', 'node02', 1);
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+};
+is($@, "", "instance on invalid node (as soft_volume) returned with find_invalid = 1");
+
+# request from a soft_volume for an instance which is on an invalid node (find_invalid = 0) [SOFT, 0]
+Test::Nebulous->setup;
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', '~node02');
+    Test::Nebulous->switch_node_state;
+
+    my $locations = $neb->find_instances('foo', 'node02', 0);
+};
+like($@, qr/database error/, "instance on invalid node (as soft_volume) not returned (find_invalid = 0)");
+
+# request from a hard_volume for an instance which is on an invalid node (find_instance not supplied) [SOFT, X]
+Test::Nebulous->setup;
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', '~node02');
+    Test::Nebulous->switch_node_state;
+
+    my $locations = $neb->find_instances('foo', 'node02');
+};
+like($@, qr/no instances available for key/, "instance on invalid node (as soft_volume) not returned (find_invalid not set)");
+
+# request from a hard_volume for an instance which is on an invalid node (find_invalid = 1) [HARD, 1]
+Test::Nebulous->setup;
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', '~node02');
+    Test::Nebulous->switch_node_state;
+
+    my $locations = $neb->find_instances('foo', '~node02', 1);
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+};
+is($@, "", "instance on invalid node (as hard_volume) returned with find_invalid = 1");
+
+# request from a hard_volume for an instance which is on an invalid node (find_invalid = 0) [HARD, 0]
+Test::Nebulous->setup;
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', '~node02');
+    Test::Nebulous->switch_node_state;
+
+    my $locations = $neb->find_instances('foo', '~node02', 0);
+};
+like($@, qr/no instances on storage volume/, "instance on invalid node (as hard_volume) not returned (find_invalid = 0)");
+
+# request from a hard_volume for an instance which is on an invalid node [HARD, X]
+Test::Nebulous->setup;
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', '~node02');
+    Test::Nebulous->switch_node_state;
+
+    my $locations = $neb->find_instances('foo', '~node02');
+};
+like($@, qr/no instances on storage volume/, "instance on invalid node (as hard_volume) not returned (find_invalid not set)");
+
+#### TESTING the instance proximity analysis
+Test::Nebulous->setup;
+{
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # place 4 instances on specific nodes (hard targets works):
+    my $uri1 = $neb->create_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo", "~node02");
+    my $uri3 = $neb->replicate_object("foo", "~node03");
+    my $uri4 = $neb->replicate_object("foo", "~node08");
+
+    my $locations = $neb->find_instances("foo", "node01");
+    my $Nloc = @$locations;
+
+    is($Nloc, 4, "found 4 instances");
+    
+    my $loc1 = $locations->[0];
+    my $loc2 = $locations->[1];
+    my $loc3 = $locations->[2];
+    my $loc4 = $locations->[3];
+
+    # choosing instance relative to node01 yields:
+    like($loc1, qr/$nodedirs[0]/, "node01 is closest to node01");    
+    like($loc2, qr/$nodedirs[1]/, "node02 is 2nd closest to node01");
+    like($loc3, qr/$nodedirs[2]/, "node03 is 3rd closest to node01");
+    like($loc4, qr/$nodedirs[7]/, "node08 is 4th closest to node01");
+}
+
+#### TESTING the instance proximity analysis
+Test::Nebulous->setup;
+{
+    my @nodedirs = Test::Nebulous->get_node_dirs();
+
+    # place 4 instances on specific nodes (hard targets works):
+    my $uri1 = $neb->create_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo", "~node02");
+    my $uri3 = $neb->replicate_object("foo", "~node03");
+    my $uri4 = $neb->replicate_object("foo", "~node08");
+
+    my $locations = $neb->find_instances("foo", "node03");
+    my $Nloc = @$locations;
+
+    is($Nloc, 4, "found 4 instances");
+    
+    my $loc1 = $locations->[0]; # 
+    my $loc2 = $locations->[1]; # 
+    my $loc3 = $locations->[2]; # 
+    my $loc4 = $locations->[3]; # 
+
+    # choosing instance relative to node01 yields:
+    like($loc1, qr/$nodedirs[2]/, "node03 is closest to node03");
+    like($loc2, qr/$nodedirs[7]/, "node08 is 2nd closest to node03");
+    like($loc3, qr/$nodedirs[1]/, "node02 is 3rd closest to node03");
+    like($loc4, qr/$nodedirs[0]/, "node08 is 4th closest to node03");
+}
+
+########
 
 Test::Nebulous->setup;
@@ -125,9 +277,9 @@
 
 Test::Nebulous->setup;
-
-eval {
-    $neb->find_instances();
-};
-like($@, qr/1 - 2 were expected/, "no params");
+eval {
+    my $locations = $neb->find_instances();
+};
+like($@, qr/1 - 3 were expected/, "no params");
+# note the new API expects 1-3 parameters
 
 Test::Nebulous->setup;
@@ -135,7 +287,8 @@
 eval {
     $neb->create_object('foo');
-    $neb->find_instances('foo', 'node01', 3);
-};
-like($@, qr/1 - 2 were expected/, "too many params");
+    $neb->find_instances('foo', 'node01', 3, 5);
+};
+like($@, qr/1 - 3 were expected/, "too many params");
+# note the new API expects 1-3 parameters
 
 Test::Nebulous->cleanup;
Index: trunk/Nebulous-Server/t/11_server_is_valid_volume_name.t
===================================================================
--- trunk/Nebulous-Server/t/11_server_is_valid_volume_name.t	(revision 24995)
+++ trunk/Nebulous-Server/t/11_server_is_valid_volume_name.t	(revision 41172)
@@ -8,5 +8,5 @@
 use warnings FATAL => qw( all );
 
-use Test::More tests => 3;
+use Test::More tests => 5;
 
 use lib qw( ./t ./lib );
@@ -31,5 +31,15 @@
 Test::Nebulous->setup;
 
-is($neb->_is_valid_volume_name('foo', 'node99'), undef, "invalid volume name");
+is($neb->_is_valid_volume_name('foo', 'node99'), 0, "invalid volume name");
+
+Test::Nebulous->setup;
+
+# key does not need to exist (is hashed to select db server)
+ok($neb->_is_valid_volume_name('bax', 'node01'), "valid volume name");
+
+Test::Nebulous->setup;
+
+# 'any' should always be a valid name
+ok($neb->_is_valid_volume_name('foo', 'any'), "valid volume name");
 
 Test::Nebulous->cleanup;
Index: trunk/Nebulous-Server/t/15_server_xattr.t
===================================================================
--- trunk/Nebulous-Server/t/15_server_xattr.t	(revision 24995)
+++ trunk/Nebulous-Server/t/15_server_xattr.t	(revision 41172)
@@ -184,6 +184,5 @@
     $neb->getxattr_object('foo', 'user.bar');
 };
-like($@, qr|xattr neb:///foo:user.bar does not exist|,
-    "get xattr from non-existant xattr key");
+like($@, qr|xattr neb:///foo:user.bar does not exist|, "get xattr from non-existant xattr key");
 
 Test::Nebulous->setup;
Index: trunk/Nebulous-Server/t/16_mounts.t
===================================================================
--- trunk/Nebulous-Server/t/16_mounts.t	(revision 24995)
+++ trunk/Nebulous-Server/t/16_mounts.t	(revision 41172)
@@ -28,5 +28,5 @@
     my $mounts = $neb->mounts();
 
-    is(scalar @$mounts, 7, "number of rows");
+    is(scalar @$mounts, 8, "number of rows");
 
     my %row;
@@ -35,12 +35,12 @@
         = @{$mounts->[0]};
     
-    is($row{total},     100000000000);
-    is($row{used},      100000000);
-    is($row{vol_id},    1);
-    is($row{name},      "node01");
-    is($row{host},      "node01");
-    is($row{allocate},  1);
-    is($row{available}, 1);
-    is($row{xattr},     0);
+    is($row{total},     100000000000, "total bytes");
+    is($row{used},      100000000, "used bytes");
+    is($row{vol_id},    1, "vol id");
+    is($row{name},      "node01", "name");
+    is($row{host},      "node01", "host");
+    is($row{allocate},  1, "is allocated");
+    is($row{available}, 1, "is available");
+    is($row{xattr},     0, "has no xattr");
 
     # 2nd row
@@ -48,12 +48,12 @@
         = @{$mounts->[1]};
 
-    is($row{total},     100000000000);
-    is($row{used},      1000000000);
-    is($row{vol_id},    2);
-    is($row{name},      "node02");
-    is($row{host},      "node02");
-    is($row{allocate},  1);
-    is($row{available}, 1);
-    is($row{xattr},     0);
+    is($row{total},     100000000000, "total bytes");
+    is($row{used},      1000000000, "used bytes");
+    is($row{vol_id},    2, "vol id");
+    is($row{name},      "node02", "name");
+    is($row{host},      "node02", "host");
+    is($row{allocate},  1, "is allocated");
+    is($row{available}, 1, "is available");
+    is($row{xattr},     0, "has no xattr");
 }
 
Index: trunk/Nebulous-Server/t/18_server_chmod_object.t
===================================================================
--- trunk/Nebulous-Server/t/18_server_chmod_object.t	(revision 24995)
+++ trunk/Nebulous-Server/t/18_server_chmod_object.t	(revision 41172)
@@ -38,4 +38,7 @@
     is($mode, 0440, "returned mode");
     is($neb->getxattr_object($key, 'user.mode'), 0440, "xattr user.mode");
+
+    # reset to 600 so the user can delete the file and /tmp entry
+    chmod(0600, $path);
 }
 
@@ -50,6 +53,9 @@
     my $mode = $neb->chmod_object($key, 0440);
 
-    my $st1 = stat(URI->new($uri1)->path);
-    my $st2 = stat(URI->new($uri2)->path);
+    my $path1 = URI->new($uri1)->path;
+    my $path2 = URI->new($uri2)->path;
+
+    my $st1 = stat($path1);
+    my $st2 = stat($path2);
 
     is($st1->mode &07777, 0440, "chmod() first instance");
@@ -57,4 +63,8 @@
     is($mode, 0440, "returned mode");
     is($neb->getxattr_object($key, 'user.mode'), 0440, "xattr user.mode");
+
+    # reset to 600 so the user can delete the file and /tmp entry
+    chmod (0600, $path1);
+    chmod (0600, $path2);
 }
 
Index: trunk/Nebulous-Server/t/20_server_find_objects_wildcard.t
===================================================================
--- trunk/Nebulous-Server/t/20_server_find_objects_wildcard.t	(revision 41172)
+++ trunk/Nebulous-Server/t/20_server_find_objects_wildcard.t	(revision 41172)
@@ -0,0 +1,109 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 12_server_find_objects.t,v 1.4 2008-05-16 20:29:19 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 22;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+Test::Nebulous->cleanup;
+
+&mktestfiles ($neb);
+{
+    my $keys = $neb->find_objects_wildcard("topdir");
+
+    is(scalar @$keys, 4, 'number of keys found');
+    is($keys->[0], "topdir/subdir/", "key name matches");
+    is($keys->[1], "topdir/object1", "key name matches");
+    is($keys->[2], "topdir/object2", "key name matches");
+    is($keys->[3], "topdir/objfoobar", "key name matches");
+}
+
+&mktestfiles ($neb);
+{
+    my $keys = $neb->find_objects_wildcard("topdir/%");
+
+    is(scalar @$keys, 4, 'number of keys found');
+    is($keys->[0], "topdir/subdir/", "key name matches");
+    is($keys->[1], "topdir/object1", "key name matches");
+    is($keys->[2], "topdir/object2", "key name matches");
+    is($keys->[3], "topdir/objfoobar", "key name matches");
+}
+
+&mktestfiles ($neb);
+{
+    my $keys = $neb->find_objects_wildcard("topdir/obj%");
+
+    is(scalar @$keys, 3, 'number of keys found');
+    is($keys->[0], "topdir/object1", "key name matches");
+    is($keys->[1], "topdir/object2", "key name matches");
+    is($keys->[2], "topdir/objfoobar", "key name matches");
+}
+
+&mktestfiles ($neb);
+{
+    my $keys = $neb->find_objects_wildcard("topdir/object%");
+
+    is(scalar @$keys, 2, 'number of keys found');
+    is($keys->[0], "topdir/object1", "key name matches");
+    is($keys->[1], "topdir/object2", "key name matches");
+}
+
+&mktestfiles ($neb);
+{
+    my $keys = $neb->find_objects_wildcard("obj%");
+
+    is(scalar @$keys, 3, 'number of keys found');
+    is($keys->[0], "object1", "key name matches");
+    is($keys->[1], "object2", "key name matches");
+    is($keys->[2], "objfoobar", "key name matches");
+}
+
+eval {
+    $neb->find_objects_wildcard("foo", 3);
+};
+like($@, qr/1 was expected/, "too many params");
+
+Test::Nebulous->cleanup;
+
+sub mktestfiles {
+    my ($neb) = $_[0];
+
+    Test::Nebulous->setup;
+    
+    # find_objects_wildcard allows these searches:
+    # -> dir : implies dir/* (dir/%)
+    # -> dir/%
+    # -> dir/sub%
+    # -> sub%
+    
+    # create examples matching the above
+    $neb->create_object("object1");
+    $neb->create_object("object2");
+    $neb->create_object("objfoobar");
+    
+    $neb->create_object("topdir/object1");
+    $neb->create_object("topdir/object2");
+    $neb->create_object("topdir/objfoobar");
+    
+    $neb->create_object("topdir/subdir/object1");
+    $neb->create_object("topdir/subdir/object2");
+    $neb->create_object("topdir/subdir/objfoobar");
+    
+    return 1;
+}
