[Rt-commit] rt branch, 4.4/core-assets, created. rt-4.2.11-43-g0f24385

Todd Wade todd at bestpractical.com
Fri Jul 24 10:21:13 EDT 2015


The branch, 4.4/core-assets has been created
        at  0f24385d6628e0a6df6533ab2bd9269bf9975c52 (commit)

- Log -----------------------------------------------------------------
commit cdabdbcb17c0999531877dfcc543764dbce63c14
Author: Todd Wade <todd at bestpractical.com>
Date:   Thu Jul 23 13:52:51 2015 -0400

    commit rt-extension-assets/xt as rt/t/assets

diff --git a/t/assets/api.t b/t/assets/api.t
new file mode 100644
index 0000000..a36d8a8
--- /dev/null
+++ b/t/assets/api.t
@@ -0,0 +1,180 @@
+use strict;
+use warnings;
+
+use lib 'xt/lib';
+use RT::Extension::Assets::Test tests => undef;
+use Test::Warn;
+
+my $catalog;
+
+diag "Create a catalog";
+{
+    $catalog = create_catalog( Name => 'Test Catalog', Disabled => 1 );
+    ok $catalog && $catalog->id, "Created catalog";
+    is $catalog->Name, "Test Catalog", "Name is correct";
+    ok $catalog->Disabled, "Disabled";
+
+    my $asset;
+    warning_like {
+        $asset = create_asset( Name => "Test", Catalog => $catalog->id );
+    } qr/^Failed to create asset .* Invalid catalog/i;
+    ok !$asset, "Couldn't create asset in disabled catalog";
+
+    my ($ok, $msg) = $catalog->SetDisabled(0);
+    ok $ok, "Enabled catalog: $msg";
+    ok !$catalog->Disabled, "Enabled";
+}
+
+diag "Create basic asset (no CFs)";
+{
+    my $asset = RT::Asset->new( RT->SystemUser );
+    my ($id, $msg) = $asset->Create(
+        Name        => 'Thinkpad T420s',
+        Description => 'Laptop',
+        Catalog     => $catalog->Name,
+    );
+    ok $id, "Created: $msg";
+    is $asset->id, $id, "id matches";
+    is $asset->Name, "Thinkpad T420s", "Name matches";
+    is $asset->Description, "Laptop", "Description matches";
+
+    # Create txn
+    my @txns = @{$asset->Transactions->ItemsArrayRef};
+    is scalar @txns, 1, "One transaction";
+    is $txns[0]->Type, "Create", "... of type Create";
+
+    # Update
+    my ($txnid, $txnmsg) = $asset->SetName("Lenovo Thinkpad T420s");
+    ok $txnid, "Updated Name: $txnmsg";
+    is $asset->Name, "Lenovo Thinkpad T420s", "New Name matches";
+
+    # Set txn
+    @txns = @{$asset->Transactions->ItemsArrayRef};
+    is scalar @txns, 2, "Two transactions";
+    is $txns[1]->Type, "Set", "... the second of which is Set";
+    is $txns[1]->Field, "Name", "... Field is Name";
+    is $txns[1]->OldValue, "Thinkpad T420s", "... OldValue is correct";
+
+    # Delete
+    my ($ok, $err) = $asset->Delete;
+    ok !$ok, "Deletes are prevented: $err";
+    $asset->Load($id);
+    ok $asset->id, "Asset not deleted";
+}
+
+diag "Create with CFs";
+{
+    my $height = create_cf( Name => 'Height' );
+    ok $height->id, "Created CF";
+
+    my $material = create_cf( Name => 'Material' );
+    ok $material->id, "Created CF";
+
+    ok apply_cfs($height, $material), "Applied CFs";
+
+    my $asset = RT::Asset->new( RT->SystemUser );
+    my ($id, $msg) = $asset->Create(
+        Name                        => 'Standing desk',
+        "CustomField-".$height->id  => '46"',
+        "CustomField-Material"      => 'pine',
+        Catalog                     => $catalog->Name,
+    );
+    ok $id, "Created: $msg";
+    is $asset->FirstCustomFieldValue('Height'), '46"', "Found height";
+    is $asset->FirstCustomFieldValue('Material'), 'pine', "Found material";
+    is $asset->Transactions->Count, 1, "Only a single txn";
+}
+
+note "Create/update with Roles";
+{
+    my $root = RT::User->new( RT->SystemUser );
+    $root->Load("root");
+    ok $root->id, "Found root";
+
+    my $bps = RT::Test->load_or_create_user( Name => "BPS" );
+    ok $bps->id, "Created BPS user";
+
+    my $asset = RT::Asset->new( RT->SystemUser );
+    my ($id, $msg) = $asset->Create(
+        Name    => 'RT server',
+        HeldBy  => $root->PrincipalId,
+        Owner   => $bps->PrincipalId,
+        Contact => $bps->PrincipalId,
+        Catalog => $catalog->id,
+    );
+    ok $id, "Created: $msg";
+    is $asset->HeldBy->UserMembersObj->First->Name, "root", "root is Holder";
+    is $asset->Owner->Name, "BPS", "BPS is Owner";
+    is $asset->Contacts->UserMembersObj->First->Name, "BPS", "BPS is Contact";
+
+    my $sysadmins = RT::Group->new( RT->SystemUser );
+    $sysadmins->CreateUserDefinedGroup( Name => 'Sysadmins' );
+    ok $sysadmins->id, "Created group";
+    is $sysadmins->Name, "Sysadmins", "Got group name";
+
+    (my $ok, $msg) = $asset->AddRoleMember(
+        Type        => 'Contact',
+        Group       => 'Sysadmins',
+    );
+    ok $ok, "Added Sysadmins as Contact: $msg";
+    is $asset->Contacts->MembersObj->Count, 2, "Found two members";
+
+    my @txn = grep { $_->Type eq 'AddWatcher' } @{$asset->Transactions->ItemsArrayRef};
+    ok @txn == 1, "Found one AddWatcher txn";
+    is $txn[0]->Field, "Contact", "... of a Contact";
+    is $txn[0]->NewValue, $sysadmins->PrincipalId, "... for the right principal";
+
+    ($ok, $msg) = $asset->DeleteRoleMember(
+        Type        => 'Contact',
+        PrincipalId => $bps->PrincipalId,
+    );
+    ok $ok, "Removed BPS user as Contact: $msg";
+    is $asset->Contacts->MembersObj->Count, 1, "Now just one member";
+    is $asset->Contacts->GroupMembersObj(Recursively => 0)->First->Name, "Sysadmins", "... it's Sysadmins";
+
+    @txn = grep { $_->Type eq 'DelWatcher' } @{$asset->Transactions->ItemsArrayRef};
+    ok @txn == 1, "Found one DelWatcher txn";
+    is $txn[0]->Field, "Contact", "... of a Contact";
+    is $txn[0]->OldValue, $bps->PrincipalId, "... for the right principal";
+}
+
+diag "Custom Field handling";
+{
+    diag "Make sure we don't load queue CFs";
+    my $queue_cf = RT::CustomField->new( RT->SystemUser );
+    my ($ok, $msg) = $queue_cf->Create(
+        Name       => "Queue CF",
+        Type       => "Text",
+        LookupType => RT::Queue->CustomFieldLookupType,
+    );
+    ok( $queue_cf->Id, "Created test CF: " . $queue_cf->Id);
+
+    my $cf1 = RT::CustomField->new( RT->SystemUser );
+    $cf1->LoadByNameAndCatalog ( Name => "Queue CF" );
+
+    ok( (not $cf1->Id), "Queue CF not loaded with LoadByNameAndCatalog");
+
+    my $cf2 = RT::CustomField->new( RT->SystemUser );
+    $cf2->LoadByNameAndCatalog ( Name => "Height" );
+    ok( $cf2->Id, "Loaded CF id: " . $cf2->Id . " with name");
+    ok( $cf2->Name, "Loaded CF name: " . $cf2->Name . " with name");
+
+    my $cf3 = RT::CustomField->new( RT->SystemUser );
+    ($ok, $msg) = $cf3->LoadByNameAndCatalog ( Name => "Height", Catalog => $catalog->Name );
+    ok( (not $cf3->Id), "CF 'Height'"
+      . " not added to catalog: " . $catalog->Name);
+
+    my $color = create_cf( Name => 'Color'  );
+    ok $color->Id, "Created CF " . $color->Name;
+    ($ok, $msg) = $color->AddToObject( $catalog );
+
+    ($ok, $msg) = $color->LoadByNameAndCatalog ( Name => "Color", Catalog => $catalog->Name );
+    ok( $color->Id, "Loaded CF id: " . $color->Id
+      . " for catalog: " . $catalog->Name);
+    ok( $color->Name, "Loaded CF name: " . $color->Name
+    . " for catalog: " . $catalog->Name);
+
+}
+
+
+done_testing;
diff --git a/t/assets/collection.t b/t/assets/collection.t
new file mode 100644
index 0000000..7bf5911
--- /dev/null
+++ b/t/assets/collection.t
@@ -0,0 +1,70 @@
+use strict;
+use warnings;
+
+use lib 'xt/lib';
+use RT::Extension::Assets::Test tests => undef;
+
+my $user = RT::Test->load_or_create_user( Name => 'testuser' );
+ok $user->id, "Created user";
+
+my $catalog  = create_catalog( Name => "BPS" );
+ok $catalog && $catalog->id, "Created catalog";
+
+my $location = create_cf( Name => 'Location' );
+ok $location->id, "Created CF";
+ok apply_cfs($location), "Applied CF";
+
+ok(
+    create_assets(
+        { Name => "Thinkpad T420s", Catalog => $catalog->id, "CustomField-Location" => "Home" },
+        { Name => "Standing desk",  Catalog => $catalog->id, "CustomField-Location" => "Office" },
+        { Name => "Chair",          Catalog => $catalog->id, "CustomField-Location" => "Office" },
+    ),
+    "Created assets"
+);
+
+diag "Mark chair as deleted";
+{
+    my $asset = RT::Asset->new( RT->SystemUser );
+    $asset->LoadByCols( Name => "Chair" );
+    my ($ok, $msg) = $asset->SetStatus( "deleted" );
+    ok($ok, "Deleted the chair: $msg");
+}
+
+diag "Basic types of limits";
+{
+    my $assets = RT::Assets->new( RT->SystemUser );
+    $assets->Limit( FIELD => 'Name', OPERATOR => 'LIKE', VALUE => 'thinkpad' );
+    is $assets->Count, 1, "Found 1 like thinkpad";
+    is $assets->First->Name, "Thinkpad T420s";
+
+    $assets = RT::Assets->new( RT->SystemUser );
+    $assets->UnLimit;
+    is $assets->Count, 2, "Found 2 total";
+    ok((!grep { $_->Name eq "Chair" } @{$assets->ItemsArrayRef}), "No chair (disabled)");
+
+    $assets = RT::Assets->new( RT->SystemUser );
+    $assets->Limit( FIELD => 'Status', VALUE => 'deleted' );
+    $assets->{allow_deleted_search} = 1;
+    is $assets->Count, 1, "Found 1 deleted";
+    is $assets->First->Name, "Chair", "Found chair";
+
+    $assets = RT::Assets->new( RT->SystemUser );
+    $assets->UnLimit;
+    $assets->LimitCustomField(
+        CUSTOMFIELD => $location->id,
+        VALUE       => "Office",
+    );
+    is $assets->Count, 1, "Found 1 in Office";
+    ok $assets->First, "Got record";
+    is $assets->First->Name, "Standing desk", "Found standing desk";
+}
+
+diag "Test ACLs";
+{
+    my $assets = RT::Assets->new( RT::CurrentUser->new($user) );
+    $assets->UnLimit;
+    is scalar @{$assets->ItemsArrayRef}, 0, "Found none";
+}
+
+done_testing;
diff --git a/t/assets/compile.t b/t/assets/compile.t
new file mode 100644
index 0000000..4ab1d21
--- /dev/null
+++ b/t/assets/compile.t
@@ -0,0 +1,11 @@
+use strict;
+use warnings;
+
+use Test::More;
+use lib 'xt/lib';
+
+use_ok('RT::Extension::Assets::Test');
+use_ok('RT::Asset');
+use_ok('RT::Assets');
+use_ok('RT::Catalog');
+use_ok('RT::Catalogs');
diff --git a/t/assets/lib/RT/Extension/Assets/Test.pm b/t/assets/lib/RT/Extension/Assets/Test.pm
new file mode 100644
index 0000000..8dbf044
--- /dev/null
+++ b/t/assets/lib/RT/Extension/Assets/Test.pm
@@ -0,0 +1,96 @@
+use strict;
+use warnings;
+
+### after: use lib qw(@RT_LIB_PATH@);
+use lib qw(/Users/trwww/Documents/waveright/bestpractical/git/rt/local/lib ../rt/lib);
+
+package RT::Extension::Assets::Test;
+use base 'RT::Test';
+
+our @EXPORT = qw(create_catalog create_asset create_assets create_cf apply_cfs);
+
+sub import {
+    my $class = shift;
+    my %args  = @_;
+
+    $args{'requires'} ||= [];
+    if ( $args{'testing'} ) {
+        unshift @{ $args{'requires'} }, 'RT::Extension::Assets';
+    } else {
+        $args{'testing'} = 'RT::Extension::Assets';
+    }
+
+    $class->SUPER::import( %args );
+
+    require RT::Extension::Assets;
+    require RT::Asset;
+    __PACKAGE__->export_to_level(1);
+}
+
+sub diag {
+    Test::More::diag(@_) if $ENV{TEST_VERBOSE};
+}
+
+sub create_catalog {
+    my %info  = @_;
+    my $catalog = RT::Catalog->new( RT->SystemUser );
+    my ($id, $msg) = $catalog->Create( %info );
+    if ($id) {
+        diag("Created catalog #$id: " . $catalog->Name);
+        return $catalog;
+    } else {
+        my $spec = join "/", map { "$_=$info{$_}" } keys %info;
+        RT->Logger->error("Failed to create catalog ($spec): $msg");
+        return;
+    }
+}
+
+sub create_asset {
+    my %info  = @_;
+    my $asset = RT::Asset->new( RT->SystemUser );
+    my ($id, $msg) = $asset->Create( %info );
+    if ($id) {
+        diag("Created asset #$id: " . $asset->Name);
+        return $asset;
+    } else {
+        my $spec = join "/", map { "$_=$info{$_}" } keys %info;
+        RT->Logger->error("Failed to create asset ($spec): $msg");
+        return;
+    }
+}
+
+sub create_assets {
+    my $error = 0;
+    for my $info (@_) {
+        create_asset(%$info)
+            or $error++;
+    }
+    return not $error;
+}
+
+sub create_cf {
+    my %args = (
+        Name        => "Test Asset CF ".($$ + rand(1024)),
+        Type        => "FreeformSingle",
+        LookupType  => RT::Asset->CustomFieldLookupType,
+        @_,
+    );
+    my $cf = RT::CustomField->new( RT->SystemUser );
+    my ($ok, $msg) = $cf->Create(%args);
+    RT->Logger->error("Can't create CF: $msg") unless $ok;
+    return $cf;
+}
+
+sub apply_cfs {
+    my $success = 1;
+    for my $cf (@_) {
+        my ($ok, $msg) = $cf->AddToObject( RT::Catalog->new(RT->SystemUser) );
+        if (not $ok) {
+            RT->Logger->error("Couldn't apply CF: $msg");
+            $success = 0;
+        }
+    }
+    return $success;
+}
+
+1;
diff --git a/t/assets/lib/RT/Extension/Assets/Test.pm.in b/t/assets/lib/RT/Extension/Assets/Test.pm.in
new file mode 100644
index 0000000..0f49438
--- /dev/null
+++ b/t/assets/lib/RT/Extension/Assets/Test.pm.in
@@ -0,0 +1,96 @@
+use strict;
+use warnings;
+
+### after: use lib qw(@RT_LIB_PATH@);
+use lib qw(/opt/rt4/local/lib /opt/rt4/lib);
+
+package RT::Extension::Assets::Test;
+use base 'RT::Test';
+
+our @EXPORT = qw(create_catalog create_asset create_assets create_cf apply_cfs);
+
+sub import {
+    my $class = shift;
+    my %args  = @_;
+
+    $args{'requires'} ||= [];
+    if ( $args{'testing'} ) {
+        unshift @{ $args{'requires'} }, 'RT::Extension::Assets';
+    } else {
+        $args{'testing'} = 'RT::Extension::Assets';
+    }
+
+    $class->SUPER::import( %args );
+
+    require RT::Extension::Assets;
+    require RT::Asset;
+    __PACKAGE__->export_to_level(1);
+}
+
+sub diag {
+    Test::More::diag(@_) if $ENV{TEST_VERBOSE};
+}
+
+sub create_catalog {
+    my %info  = @_;
+    my $catalog = RT::Catalog->new( RT->SystemUser );
+    my ($id, $msg) = $catalog->Create( %info );
+    if ($id) {
+        diag("Created catalog #$id: " . $catalog->Name);
+        return $catalog;
+    } else {
+        my $spec = join "/", map { "$_=$info{$_}" } keys %info;
+        RT->Logger->error("Failed to create catalog ($spec): $msg");
+        return;
+    }
+}
+
+sub create_asset {
+    my %info  = @_;
+    my $asset = RT::Asset->new( RT->SystemUser );
+    my ($id, $msg) = $asset->Create( %info );
+    if ($id) {
+        diag("Created asset #$id: " . $asset->Name);
+        return $asset;
+    } else {
+        my $spec = join "/", map { "$_=$info{$_}" } keys %info;
+        RT->Logger->error("Failed to create asset ($spec): $msg");
+        return;
+    }
+}
+
+sub create_assets {
+    my $error = 0;
+    for my $info (@_) {
+        create_asset(%$info)
+            or $error++;
+    }
+    return not $error;
+}
+
+sub create_cf {
+    my %args = (
+        Name        => "Test Asset CF ".($$ + rand(1024)),
+        Type        => "FreeformSingle",
+        LookupType  => RT::Asset->CustomFieldLookupType,
+        @_,
+    );
+    my $cf = RT::CustomField->new( RT->SystemUser );
+    my ($ok, $msg) = $cf->Create(%args);
+    RT->Logger->error("Can't create CF: $msg") unless $ok;
+    return $cf;
+}
+
+sub apply_cfs {
+    my $success = 1;
+    for my $cf (@_) {
+        my ($ok, $msg) = $cf->AddToObject( RT::Catalog->new(RT->SystemUser) );
+        if (not $ok) {
+            RT->Logger->error("Couldn't apply CF: $msg");
+            $success = 0;
+        }
+    }
+    return $success;
+}
+
+1;
diff --git a/t/assets/links.t b/t/assets/links.t
new file mode 100644
index 0000000..d81fdcc
--- /dev/null
+++ b/t/assets/links.t
@@ -0,0 +1,130 @@
+use strict;
+use warnings;
+
+use lib 'xt/lib';
+use RT::Extension::Assets::Test tests => undef;
+use Test::Warn;
+
+my $catalog = create_catalog( Name => "BPS" );
+ok $catalog && $catalog->id, "Created Catalog";
+
+ok(
+    create_assets(
+        { Name => "Thinkpad T420s", Catalog => $catalog->id },
+        { Name => "Standing desk", Catalog => $catalog->id },
+        { Name => "Chair", Catalog => $catalog->id },
+    ),
+    "Created assets"
+);
+
+my $ticket = RT::Test->create_ticket(
+    Queue   => 1,
+    Subject => 'a test ticket',
+);
+ok $ticket->id, "Created ticket";
+
+diag "RT::URI::asset";
+{
+    my %uris = (
+        # URI                   => Asset Name
+        "asset:1"               => { id => 1, Name => "Thinkpad T420s" },
+        "asset://example.com/2" => { id => 2, Name => "Standing desk" },
+        "asset:13"              => undef,
+    );
+
+    while (my ($url, $expected) = each %uris) {
+        my $uri = RT::URI->new( RT->SystemUser );
+        if ($expected) {
+            my $parsed = $uri->FromURI($url);
+            ok $parsed, "Parsed $url";
+
+            my $asset = $uri->Object;
+            ok $asset, "Got object";
+            is ref($asset), "RT::Asset", "... it's a RT::Asset";
+
+            while (my ($field, $value) = each %$expected) {
+                is $asset->$field, $value, "... $field is $value";
+            }
+        } else {
+            my $parsed;
+            warnings_like {
+                $parsed = $uri->FromURI($url);
+            } [qr/\Q$url\E/, qr/\Q$url\E/], "Caught warnings about unknown URI";
+            ok !$parsed, "Failed to parse $url, as expected";
+        }
+    }
+}
+
+diag "RT::Asset link support";
+{
+    my $chair = RT::Asset->new( RT->SystemUser );
+    $chair->LoadByCols( Name => "Chair" );
+    ok $chair->id, "Loaded asset";
+    is $chair->URI, "asset://example.com/".$chair->id, "->URI works";
+
+    my ($link_id, $msg) = $chair->AddLink( Type => 'MemberOf', Target => 'asset:2' );
+    ok $link_id, "Added link: $msg";
+
+    my $parents = $chair->MemberOf;
+    my $desk    = $parents->First->TargetObj;
+    is $parents->Count, 1, "1 parent";
+    is $desk->Name, "Standing desk", "Correct parent asset";
+
+    for my $asset ($chair, $desk) {
+        my $txns = $asset->Transactions;
+        $txns->Limit( FIELD => 'Type', VALUE => 'AddLink' );
+        is $txns->Count, 1, "1 AddLink txn on asset ".$asset->Name;
+    }
+
+    my ($ok, $err) = $chair->DeleteLink( Type => 'MemberOf', Target => 'asset:1' );
+    ok !$ok, "Delete link failed on non-existent: $err";
+
+    my ($deleted, $delete_msg) = $chair->DeleteLink( Type => 'MemberOf', Target => $parents->First->Target );
+    ok $deleted, "Deleted link: $delete_msg";
+
+    for my $asset ($chair, $desk) {
+        my $txns = $asset->Transactions;
+        $txns->Limit( FIELD => 'Type', VALUE => 'DeleteLink' );
+        is $txns->Count, 1, "1 DeleteLink txn on asset ".$asset->Name;
+    }
+};
+
+diag "Linking to tickets";
+{
+    my $laptop = RT::Asset->new( RT->SystemUser );
+    $laptop->LoadByCols( Name => "Thinkpad T420s" );
+
+    my ($ok, $msg) = $ticket->AddLink( Type => 'RefersTo', Target => $laptop->URI );
+    ok $ok, "Ticket refers to asset: $msg";
+
+    my $links = $laptop->ReferredToBy;
+    is $links->Count, 1, "Found a ReferredToBy link via asset";
+
+    ($ok, $msg) = $laptop->DeleteLink( Type => 'RefersTo', Base => $ticket->URI );
+    ok $ok, "Deleted link from opposite side: $msg";
+}
+
+diag "Links on ->Create";
+{
+    my $desk = RT::Asset->new( RT->SystemUser );
+    $desk->LoadByCols( Name => "Standing desk" );
+    ok $desk->id, "Loaded standing desk asset";
+
+    my $asset = create_asset(
+        Name            => "Anti-fatigue mat",
+        Catalog         => $catalog->id,
+        Parent          => $desk->URI,
+        ReferredToBy    => [$ticket->id],
+    );
+    ok $asset->id, "Created asset with Parent link";
+
+    my $parents = $asset->MemberOf;
+    is $parents->Count, 1, "Found one Parent";
+    is $parents->First->Target, $desk->URI, "... it's a desk!";
+
+    my $referrals = $asset->ReferredToBy;
+    is $referrals->Count, 1, "Found one ReferredToBy";
+    is $referrals->First->Base, $ticket->URI, "... it's the ticket!";
+}
+
+done_testing;
diff --git a/t/assets/pod.t b/t/assets/pod.t
new file mode 100644
index 0000000..1d2686c
--- /dev/null
+++ b/t/assets/pod.t
@@ -0,0 +1,6 @@
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Pod;
+all_pod_files_ok( all_pod_files("lib","doc","etc"));
diff --git a/t/assets/rights.t b/t/assets/rights.t
new file mode 100644
index 0000000..30394b3
--- /dev/null
+++ b/t/assets/rights.t
@@ -0,0 +1,125 @@
+use strict;
+use warnings;
+
+use lib 'xt/lib';
+use RT::Extension::Assets::Test tests => undef;
+
+my $user = RT::Test->load_or_create_user( Name => 'testuser' );
+ok $user->id, "Created user";
+
+my $ticket = RT::Test->create_ticket(
+    Queue   => 1,
+    Subject => 'a test ticket',
+);
+ok $ticket->id, "Created ticket";
+
+my $catalog_one = create_catalog( Name => "One" );
+ok $catalog_one && $catalog_one->id, "Created catalog one";
+
+my $catalog_two = create_catalog( Name => "Two" );
+ok $catalog_two && $catalog_two->id, "Created catalog two";
+
+ok(RT::Test->add_rights({
+    Principal   => 'Privileged',
+    Right       => 'ShowCatalog',
+    Object      => $catalog_one,
+}), "Granted ShowCatalog");
+
+my $asset = RT::Asset->new( RT::CurrentUser->new($user) );
+
+diag "CreateAsset";
+{
+    my %create = (
+        Name    => 'Thinkpad T420s',
+        Contact => 'trs at example.com',
+        Catalog => $catalog_one->id,
+    );
+    my ($id, $msg) = $asset->Create(%create);
+    ok !$id, "Create denied: $msg";
+
+    ok(RT::Test->add_rights({
+        Principal   => 'Privileged',
+        Right       => 'CreateAsset',
+        Object      => $catalog_one,
+    }), "Granted CreateAsset");
+
+    ($id, $msg) = $asset->Create(%create);
+    ok $id, "Created: $msg";
+    is $asset->id, $id, "id matches";
+    is $asset->CatalogObj->Name, $catalog_one->Name, "Catalog matches";
+};
+
+diag "ShowAsset";
+{
+    is $asset->Name, undef, "Can't see Name without ShowAsset";
+    ok !$asset->Contacts->id, "Can't see Contacts role group";
+
+    ok(RT::Test->add_rights({
+        Principal   => 'Privileged',
+        Right       => 'ShowAsset',
+        Object      => $catalog_one,
+    }), "Granted ShowAsset");
+
+    is $asset->Name, "Thinkpad T420s", "Got Name";
+    is $asset->Contacts->UserMembersObj->First->EmailAddress, 'trs at example.com', "Got Contact";
+}
+
+diag "ModifyAsset";
+{
+    my ($txnid, $txnmsg) = $asset->SetName("Lenovo Thinkpad T420s");
+    ok !$txnid, "Update failed: $txnmsg";
+    is $asset->Name, "Thinkpad T420s", "Name didn't change";
+
+    my ($ok, $msg) = $asset->AddLink( Type => 'RefersTo', Target => 't:1' );
+    ok !$ok, "No rights to AddLink: $msg";
+
+    ($ok, $msg) = $asset->DeleteLink( Type => 'RefersTo', Target => 't:1' );
+    ok !$ok, "No rights to DeleteLink: $msg";
+
+    ok(RT::Test->add_rights({
+        Principal   => 'Privileged',
+        Right       => 'ModifyAsset',
+        Object      => $catalog_one,
+    }), "Granted ModifyAsset");
+    
+    ($txnid, $txnmsg) = $asset->SetName("Lenovo Thinkpad T420s");
+    ok $txnid, "Updated Name: $txnmsg";
+    is $asset->Name, "Lenovo Thinkpad T420s", "Name changed";
+}
+
+diag "Catalogs";
+{
+    my ($txnid, $txnmsg) = $asset->SetCatalog($catalog_two->id);
+    ok !$txnid, "Failed to update Catalog: $txnmsg";
+    is $asset->CatalogObj->Name, $catalog_one->Name, "Catalog unchanged";
+
+    ok(RT::Test->add_rights({
+        Principal   => 'Privileged',
+        Right       => 'CreateAsset',
+        Object      => $catalog_two,
+    }), "Granted CreateAsset in second catalog");
+
+    ($txnid, $txnmsg) = $asset->SetCatalog($catalog_two->id);
+    ok $txnid, "Updated Catalog: $txnmsg";
+    unlike $txnmsg, qr/Permission Denied/i, "Transaction message isn't Permission Denied";
+    ok !$asset->CurrentUserCanSee, "Can no longer see the asset";
+
+    ok(RT::Test->add_rights({
+        Principal   => 'Privileged',
+        Right       => 'ShowAsset',
+        Object      => $catalog_two,
+    }), "Granted ShowAsset");
+
+    ok $asset->CurrentUserCanSee, "Can see the asset now";
+    is $asset->CatalogObj->Name, undef, "Can't see the catalog name still";
+
+    ok(RT::Test->add_rights({
+        Principal   => 'Privileged',
+        Right       => 'ShowCatalog',
+        Object      => $catalog_two,
+    }), "Granted ShowCatalog");
+
+    is $asset->CatalogObj->Name, $catalog_two->Name, "Now we can see the catalog name";
+}
+
+done_testing;
diff --git a/t/assets/roles.t b/t/assets/roles.t
new file mode 100644
index 0000000..ffaa5d2
--- /dev/null
+++ b/t/assets/roles.t
@@ -0,0 +1,30 @@
+use strict;
+use warnings;
+
+use lib 'xt/lib';
+use RT::Extension::Assets::Test tests => undef;
+
+my $catalog = create_catalog( Name => "A catalog" );
+my $asset = create_asset( Name => "Test asset", Catalog => $catalog->id );
+ok $asset && $asset->id, "Created asset";
+
+for my $object ($asset, $catalog, RT->System) {
+    for my $role (RT::Asset->Roles) {
+        my $group = $object->RoleGroup($role);
+        ok $group->id, "Loaded role group $role for " . ref($object);
+
+        my $principal = $group->PrincipalObj;
+        ok $principal && $principal->id, "Found PrincipalObj for role group"
+            or next;
+
+        if ($object->DOES("RT::Record::Role::Rights")) {
+            my ($ok, $msg) = $principal->GrantRight(
+                Object  => $object,
+                Right   => "ShowAsset",
+            );
+            ok $ok, "Granted right" or diag "Error: $msg";
+        }
+    }
+}
+
+done_testing;
diff --git a/t/assets/web.t b/t/assets/web.t
new file mode 100644
index 0000000..5579702
--- /dev/null
+++ b/t/assets/web.t
@@ -0,0 +1,114 @@
+use strict;
+use warnings;
+
+use lib 'xt/lib';
+use RT::Extension::Assets::Test tests => undef;
+
+RT->Config->Set("CustomFieldGroupings",
+    "RT::Asset" => {
+        Dates => [qw(Purchased)],
+    },
+);
+
+my $catalog = create_catalog( Name => "Office" );
+ok $catalog->id, "Created Catalog";
+
+my $purchased = create_cf( Name => 'Purchased', Pattern => '(?#Year)^(?:19|20)\d{2}$' );
+ok $purchased->id, "Created CF";
+
+my $height = create_cf( Name => 'Height', Pattern => '(?#Inches)^\d+"?$' );
+ok $height->id, "Created CF";
+
+my $material = create_cf( Name => 'Material' );
+ok $material->id, "Created CF";
+
+my %CF = (
+    Height      => ".CF-" . $height->id    . "-Edit",
+    Material    => ".CF-" . $material->id  . "-Edit",
+    Purchased   => ".CF-" . $purchased->id . "-Edit",
+);
+
+my ($base, $m) = RT::Extension::Assets::Test->started_ok;
+ok $m->login, "Logged in agent";
+
+diag "Create basic asset (no CFs)";
+{
+    $m->follow_link_ok({ id => "assets-create" }, "Asset create link");
+    $m->submit_form_ok({ with_fields => { Catalog => $catalog->id } }, "Picked a catalog");
+    $m->submit_form_ok({
+        with_fields => {
+            id          => 'new',
+            Name        => 'Thinkpad T420s',
+            Description => 'A laptop',
+        },
+    }, "submited create form");
+    $m->content_like(qr/Asset .* created/, "Found created message");
+    my ($id) = $m->uri =~ /id=(\d+)/;
+
+    my $asset = RT::Asset->new( RT->SystemUser );
+    $asset->Load($id);
+    is $asset->id, $id, "id matches";
+    is $asset->Name, "Thinkpad T420s", "Name matches";
+    is $asset->Description, "A laptop", "Description matches";
+}
+
+diag "Create with CFs";
+{
+    ok apply_cfs($height, $material), "Applied CFs";
+
+    $m->follow_link_ok({ id => "assets-create" }, "Asset create link");
+    $m->submit_form_ok({ with_fields => { Catalog => $catalog->id } }, "Picked a catalog");
+
+    ok $m->form_with_fields(qw(id Name Description)), "Found form";
+    $m->submit_form_ok({
+        fields => {
+            id              => 'new',
+            Name            => 'Standing desk',
+            $CF{Height}     => 'forty-six inches',
+            $CF{Material}   => 'pine',
+        },
+    }, "submited create form");
+    $m->content_unlike(qr/Asset .* created/, "Lacks created message");
+    $m->content_like(qr/must match .*?Inches/, "Found validation error");
+
+    # Intentionally fix only the invalid CF to test the other fields are
+    # preserved across errors
+    ok $m->form_with_fields(qw(id Name Description)), "Found form again";
+    $m->set_fields( $CF{Height} => '46"' );
+    $m->submit_form_ok({}, "resubmitted form");
+
+    $m->content_like(qr/Asset .* created/, "Found created message");
+    my ($id) = $m->uri =~ /id=(\d+)/;
+
+    my $asset = RT::Asset->new( RT->SystemUser );
+    $asset->Load($id);
+    is $asset->id, $id, "id matches";
+    is $asset->FirstCustomFieldValue('Height'), '46"', "Found height";
+    is $asset->FirstCustomFieldValue('Material'), 'pine', "Found material";
+}
+
+diag "Create with CFs in other groups";
+{
+    ok apply_cfs($purchased), "Applied CF";
+
+    $m->follow_link_ok({ id => "assets-create" }, "Asset create link");
+    $m->submit_form_ok({ with_fields => { Catalog => $catalog->id } }, "Picked a catalog");
+
+    ok $m->form_with_fields(qw(id Name Description)), "Found form";
+
+    $m->submit_form_ok({
+        fields => {
+            id          => 'new',
+            Name        => 'Chair',
+            $CF{Height} => '23',
+        },
+    }, "submited create form");
+
+    $m->content_like(qr/Asset .* created/, "Found created message");
+    $m->content_unlike(qr/Purchased.*?must match .*?Year/, "Lacks validation error for Purchased");
+}
+
+# XXX TODO: test other modify pages
+
+undef $m;
+done_testing;

commit 6eb052012c1f4b54a8ee32413ef174b11eb0f102
Author: Todd Wade <todd at bestpractical.com>
Date:   Thu Jul 23 13:53:36 2015 -0400

    remove .in file and use .pm file as test lib

diff --git a/t/assets/lib/RT/Extension/Assets/Test.pm.in b/t/assets/lib/RT/Extension/Assets/Test.pm.in
deleted file mode 100644
index 0f49438..0000000
--- a/t/assets/lib/RT/Extension/Assets/Test.pm.in
+++ /dev/null
@@ -1,96 +0,0 @@
-use strict;
-use warnings;
-
-### after: use lib qw(@RT_LIB_PATH@);
-use lib qw(/opt/rt4/local/lib /opt/rt4/lib);
-
-package RT::Extension::Assets::Test;
-use base 'RT::Test';
-
-our @EXPORT = qw(create_catalog create_asset create_assets create_cf apply_cfs);
-
-sub import {
-    my $class = shift;
-    my %args  = @_;
-
-    $args{'requires'} ||= [];
-    if ( $args{'testing'} ) {
-        unshift @{ $args{'requires'} }, 'RT::Extension::Assets';
-    } else {
-        $args{'testing'} = 'RT::Extension::Assets';
-    }
-
-    $class->SUPER::import( %args );
-
-    require RT::Extension::Assets;
-    require RT::Asset;
-    __PACKAGE__->export_to_level(1);
-}
-
-sub diag {
-    Test::More::diag(@_) if $ENV{TEST_VERBOSE};
-}
-
-sub create_catalog {
-    my %info  = @_;
-    my $catalog = RT::Catalog->new( RT->SystemUser );
-    my ($id, $msg) = $catalog->Create( %info );
-    if ($id) {
-        diag("Created catalog #$id: " . $catalog->Name);
-        return $catalog;
-    } else {
-        my $spec = join "/", map { "$_=$info{$_}" } keys %info;
-        RT->Logger->error("Failed to create catalog ($spec): $msg");
-        return;
-    }
-}
-
-sub create_asset {
-    my %info  = @_;
-    my $asset = RT::Asset->new( RT->SystemUser );
-    my ($id, $msg) = $asset->Create( %info );
-    if ($id) {
-        diag("Created asset #$id: " . $asset->Name);
-        return $asset;
-    } else {
-        my $spec = join "/", map { "$_=$info{$_}" } keys %info;
-        RT->Logger->error("Failed to create asset ($spec): $msg");
-        return;
-    }
-}
-
-sub create_assets {
-    my $error = 0;
-    for my $info (@_) {
-        create_asset(%$info)
-            or $error++;
-    }
-    return not $error;
-}
-
-sub create_cf {
-    my %args = (
-        Name        => "Test Asset CF ".($$ + rand(1024)),
-        Type        => "FreeformSingle",
-        LookupType  => RT::Asset->CustomFieldLookupType,
-        @_,
-    );
-    my $cf = RT::CustomField->new( RT->SystemUser );
-    my ($ok, $msg) = $cf->Create(%args);
-    RT->Logger->error("Can't create CF: $msg") unless $ok;
-    return $cf;
-}
-
-sub apply_cfs {
-    my $success = 1;
-    for my $cf (@_) {
-        my ($ok, $msg) = $cf->AddToObject( RT::Catalog->new(RT->SystemUser) );
-        if (not $ok) {
-            RT->Logger->error("Couldn't apply CF: $msg");
-            $success = 0;
-        }
-    }
-    return $success;
-}
-
-1;

commit 422e0893f84b333fb8144e0fa2d0e6ce70c12bf8
Author: Todd Wade <todd at bestpractical.com>
Date:   Thu Jul 23 14:59:22 2015 -0400

    move extension test lib in to RT

diff --git a/t/assets/lib/RT/Extension/Assets/Test.pm b/lib/RT/Test/Assets.pm
similarity index 100%
rename from t/assets/lib/RT/Extension/Assets/Test.pm
rename to lib/RT/Test/Assets.pm

commit 322fbe2044f2bf15aa3a90d2da19d061e6556b2f
Author: Todd Wade <todd at bestpractical.com>
Date:   Thu Jul 23 17:28:10 2015 -0400

    clean up RT::Test::Asset lib so it can be used as a base for the tests

diff --git a/lib/RT/Test/Assets.pm b/lib/RT/Test/Assets.pm
index 8dbf044..f107a12 100644
--- a/lib/RT/Test/Assets.pm
+++ b/lib/RT/Test/Assets.pm
@@ -1,10 +1,7 @@
 use strict;
 use warnings;
 
-### after: use lib qw(@RT_LIB_PATH@);
-use lib qw(/Users/trwww/Documents/waveright/bestpractical/git/rt/local/lib ../rt/lib);
-
-package RT::Extension::Assets::Test;
+package RT::Test::Assets;
 use base 'RT::Test';
 
 our @EXPORT = qw(create_catalog create_asset create_assets create_cf apply_cfs);
@@ -13,13 +10,6 @@ sub import {
     my $class = shift;
     my %args  = @_;
 
-    $args{'requires'} ||= [];
-    if ( $args{'testing'} ) {
-        unshift @{ $args{'requires'} }, 'RT::Extension::Assets';
-    } else {
-        $args{'testing'} = 'RT::Extension::Assets';
-    }
-
     $class->SUPER::import( %args );
 
     require RT::Extension::Assets;

commit 3b71f69acb2b18ccbb56c1ab8fa7fe7908aae0de
Author: Todd Wade <todd at bestpractical.com>
Date:   Thu Jul 23 17:29:51 2015 -0400

    move assets database scaffolding to RT
    
    This is mostly a raw copy in to the RT schema files.
    
    TODO:
    - integrate in to existing data instead of adding to end of file
    - upgrading without Assets extension installed
    - upgrading with Assets extension installed

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index fd9e971..bb7c095 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1644,7 +1644,138 @@ Set($HideArticleSearchOnReplyCreate, 0);
 
 =back
 
+=head1 Assets
 
+=over 4
+
+=item C<@AssetQueues>
+
+This should be a list of names of queues whose tickets should always
+display the "Assets" box.  This is useful for queues which deal
+primarily with assets, as it provides a ready box to link an asset to
+the ticket, even when the ticket has no related assets yet.
+
+=cut
+
+# Set(@AssetQueues, ());
+
+=item C<$DefaultCatalog>
+
+This provides the default catalog after a user initially logs in.
+However, the default catalog is "sticky," and so will remember the
+last-selected catalog thereafter.
+
+=cut
+
+# Set($DefaultCatalog, 'General assets');
+
+=item C<$AssetSearchFields>
+
+Specifies which fields of L<RT::Asset> to match against and how to match
+each field when performing a quick search on assets.  Valid match
+methods are LIKE, STARTSWITH, ENDSWITH, =, and !=.  Valid search fields
+are id, Name, Description, or custom fields, which are specified as
+"CF.1234" or "CF.Name"
+
+=cut
+
+Set($AssetSearchFields, {
+    id          => '=',
+    Name        => 'LIKE',
+    Description => 'LIKE',
+}) unless $AssetSearchFields;
+
+=item C<$AssetSearchFormat>
+
+The format that results of the asset search are displayed with.  This is
+either a string, which will be used for all catalogs, or a hash
+reference, keyed by catalog's name/id.  If a hashref and neither name or
+id is found therein, falls back to the key ''.
+
+If you wish to use the multiple catalog format, your configuration would look
+something like:
+
+    Set($AssetSearchFormat, {
+        'General assets' => q[Format String for the General Assets Catalog],
+        8                => q[Format String for Catalog 8],
+        ''               => q[Format String for any catalogs not listed explicitly],
+    });
+
+=cut
+
+# loc('Related tickets')
+Set($AssetSearchFormat, q[
+    '<a href="__WebHomePath__/Asset/Display.html?id=__id__">__Name__</a>/TITLE:Name',
+    Description,
+    '__Status__ (__Catalog__)/TITLE:Status',
+    Owner,
+    HeldBy,
+    Contacts,
+    '__ActiveTickets__ __InactiveTickets__/TITLE:Related tickets',
+]) unless $AssetSearchFormat;
+
+=item C<$AssetSummaryFormat>
+
+The information that is displayed on ticket display pages about assets
+related to the ticket.  This is displayed in a table beneath the asset
+name.
+
+=cut
+
+Set($AssetSummaryFormat, q[
+    '<a href="__WebHomePath__/Asset/Display.html?id=__id__">__Name__</a>/TITLE:Name',
+    Description,
+    '__Status__ (__Catalog__)/TITLE:Status',
+    Owner,
+    HeldBy,
+    Contacts,
+    '__ActiveTickets__ __InactiveTickets__/TITLE:Related tickets',
+]) unless $AssetSummaryFormat;
+
+=item C<$AssetSummaryRelatedTicketsFormat>
+
+The information that is displayed on ticket display pages about tickets
+related to assets related to the ticket.  This is displayed as a list of
+tickets underneath the asset properties.
+
+=cut
+
+Set($AssetSummaryRelatedTicketsFormat, q[
+    '<a href="__WebPath__/Ticket/Display.html?id=__id__">__id__</a>',
+    '(__OwnerName__)',
+    '<a href="__WebPath__/Ticket/Display.html?id=__id__">__Subject__</a>',
+    QueueName,
+    Status,
+]) unless $AssetSummaryRelatedTicketsFormat;
+
+=item C<%AdminSearchResultFormat>
+
+The C<Catalogs> key of this standard RT configuration option (see
+L<RT_Config/%AdminSearchResultFormat>) controls how catalogs are
+displayed in their list in the admin pages.
+
+=cut
+
+Set(%AdminSearchResultFormat,
+    Catalogs =>
+        q{'<a href="__WebPath__/Admin/Assets/Catalogs/Modify.html?id=__id__">__id__</a>/TITLE:#'}
+        .q{,'<a href="__WebPath__/Admin/Assets/Catalogs/Modify.html?id=__id__">__Name__</a>/TITLE:Name'}
+        .q{,__Description__,__Lifecycle__,__Disabled__},
+) unless $AdminSearchResultFormat{Catalogs};
+
+=item C<$AssetBasicCustomFieldsOnCreate>
+
+Specify a list of Asset custom fields to show in "Basics" widget on create.
+
+e.g.
+
+Set( $AssetBasicCustomFieldsOnCreate, [ 'foo', 'bar' ] );
+
+=cut
+
+# Set($AssetBasicCustomFieldsOnCreate, undef );
+
+=back
 
 =head2 Message box properties
 
@@ -2359,8 +2490,6 @@ Set($TimeInICal, 0);
 
 =back
 
-
-
 =head1 Cryptography
 
 A complete description of RT's cryptography capabilities can be found in
@@ -2875,6 +3004,52 @@ Set(%Lifecycles,
             'deleted -> open'  => { label  => 'Undelete',                    }, # loc{label}
         ],
     },
+    assets => {
+        type     => "asset",
+        initial  => [ 
+            'new' # loc
+        ],
+        active   => [ 
+            'allocated', # loc
+            'in-use' # loc
+        ],
+        inactive => [ 
+            'recycled', # loc
+            'stolen', # loc
+            'deleted' # loc
+        ],
+
+        defaults => {
+            on_create => 'new',
+        },
+
+        transitions => {
+            ''        => [qw(new allocated in-use)],
+            new       => [qw(allocated in-use stolen deleted)],
+            allocated => [qw(in-use recycled stolen deleted)],
+            "in-use"  => [qw(allocated recycled stolen deleted)],
+            recycled  => [qw(allocated)],
+            stolen    => [qw(allocated)],
+            deleted   => [qw(allocated)],
+        },
+        rights => {
+            '* -> *'        => 'ModifyAsset',
+        },
+        actions => {
+            '* -> allocated' => { 
+                label => "Allocate" # loc
+            },
+            '* -> in-use'    => { 
+                label => "Now in-use" # loc
+            },
+            '* -> recycled'  => { 
+                label => "Recycle" # loc
+            },
+            '* -> stolen'    => { 
+                label => "Report stolen" # loc
+            },
+        },
+    },
 );
 
 
diff --git a/etc/acl.Pg b/etc/acl.Pg
index a659d8e..0e1de2c 100644
--- a/etc/acl.Pg
+++ b/etc/acl.Pg
@@ -80,6 +80,35 @@ sub acl {
         }
     }
     return (@acls);
+
+{ # START assets ACL
+    my @tables = qw (
+        rtxassets_id_seq
+        RTxAssets
+        rtxcatalogs_id_seq
+        RTxCatalogs
+    );
+
+    my $db_user = RT->Config->Get('DatabaseUser');
+
+    my $sequence_right
+        = ( $dbh->{pg_server_version} >= 80200 )
+        ? "USAGE, SELECT, UPDATE"
+        : "SELECT, UPDATE";
+
+    foreach my $table (@tables) {
+        # Tables are upper-case, sequences are lowercase in @tables
+        if ( $table =~ /^[a-z]/ ) {
+            push @acls, "GRANT $sequence_right ON $table TO \"$db_user\";"
+        }
+        else {
+            push @acls, "GRANT SELECT, INSERT, UPDATE, DELETE ON $table TO \"$db_user\";"
+        }
+    }
+
+} # END Assets ACL
+
+    return (@acls);
 }
 
 1;
diff --git a/etc/initialdata b/etc/initialdata
index dd1daf5..f876940 100644
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -886,3 +886,33 @@ Hour:         { $SubscriptionObj->SubValue('Hour') }
         },
     },
 );
+
+require RT::Asset;
+# Create global role groups
+push @Final, sub {
+    foreach my $type (RT::Asset->Roles) {
+        next if $type eq "Owner";   # There's a core global role group for Owner
+
+        my $group = RT::Group->new( RT->SystemUser );
+        my ($ok, $msg) = $group->CreateRoleGroup(
+            Object              => RT->System,
+            Name                => $type,
+            InsideTransaction   => 0,
+        );
+        RT->Logger->error("Couldn't create global asset role group '$type': $msg")
+            unless $ok;
+    }
+};
+
+# Create default catalog
+push @Final, sub {
+    my $catalog = RT::Catalog->new( RT->SystemUser );
+    my ($ok, $msg) = $catalog->Create(
+        Name        => "General assets",
+        Description => "The default catalog",
+    );
+    RT->Logger->error("Couldn't create default catalog 'General assets': $msg")
+        unless $ok;
+};
+
+1;
diff --git a/etc/schema.Oracle b/etc/schema.Oracle
index effefc5..4934920 100644
--- a/etc/schema.Oracle
+++ b/etc/schema.Oracle
@@ -487,3 +487,35 @@ Created DATE,
 LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL,
 LastUpdated DATE
 );
+CREATE SEQUENCE RTxAssets_seq;
+CREATE TABLE RTxAssets (
+    id              NUMBER(11,0)    CONSTRAINT RTxAssets_key PRIMARY KEY,
+    Name            varchar2(255)   DEFAULT '',
+    Catalog         NUMBER(11,0)    DEFAULT 0 NOT NULL,
+    Status          varchar2(64)    DEFAULT '',
+    Description     varchar2(255)   DEFAULT '',
+    Creator         NUMBER(11,0)    DEFAULT 0 NOT NULL,
+    Created         DATE,
+    LastUpdatedBy   NUMBER(11,0)    DEFAULT 0 NOT NULL,
+    LastUpdated     DATE
+);
+
+CREATE INDEX RTxAssetsName ON RTxAssets (LOWER(Name));
+CREATE INDEX RTxAssetsStatus ON RTxAssets (Status);
+CREATE INDEX RTxAssetsCatalog ON RTxAssets (Catalog);
+
+CREATE SEQUENCE RTxCatalogs_seq;
+CREATE TABLE RTxCatalogs (
+    id              NUMBER(11,0)    CONSTRAINT RTxCatalogs_key PRIMARY KEY,
+    Name            varchar2(255)   DEFAULT '',
+    Lifecycle       varchar2(32)    DEFAULT 'assets',
+    Description     varchar2(255)   DEFAULT '',
+    Disabled        NUMBER(11,0)    DEFAULT 0 NOT NULL,
+    Creator         NUMBER(11,0)    DEFAULT 0 NOT NULL,
+    Created         DATE,
+    LastUpdatedBy   NUMBER(11,0)    DEFAULT 0 NOT NULL,
+    LastUpdated     DATE
+);
+
+CREATE INDEX RTxCatalogsName ON RTxCatalogs (LOWER(Name));
+CREATE INDEX RTxCatalogsDisabled ON RTxCatalogs (Disabled);
diff --git a/etc/schema.Pg b/etc/schema.Pg
index e5e2a04..6459483 100644
--- a/etc/schema.Pg
+++ b/etc/schema.Pg
@@ -719,3 +719,37 @@ LastUpdated TIMESTAMP NULL,
 PRIMARY KEY (id)
 );
 
+CREATE SEQUENCE rtxassets_id_seq;
+CREATE TABLE RTxAssets (
+    id                integer                  DEFAULT nextval('rtxassets_id_seq'),
+    Name              varchar(255)    NOT NULL DEFAULT '',
+    Catalog           integer         NOT NULL DEFAULT 0,
+    Status            varchar(64)     NOT NULL DEFAULT '',
+    Description       varchar(255)    NOT NULL DEFAULT '',
+    Creator           integer         NOT NULL DEFAULT 0,
+    Created           timestamp                DEFAULT NULL,
+    LastUpdatedBy     integer         NOT NULL DEFAULT 0,
+    LastUpdated       timestamp                DEFAULT NULL,
+    PRIMARY KEY (id)
+);
+
+CREATE INDEX RTxAssetsName ON RTxAssets (LOWER(Name));
+CREATE INDEX RTxAssetsStatus ON RTxAssets (Status);
+CREATE INDEX RTxAssetsCatalog ON RTxAssets (Catalog);
+
+CREATE SEQUENCE rtxcatalogs_id_seq;
+CREATE TABLE RTxCatalogs (
+    id                integer                  DEFAULT nextval('rtxcatalogs_id_seq'),
+    Name              varchar(255)    NOT NULL DEFAULT '',
+    Lifecycle         varchar(32)     NOT NULL DEFAULT 'assets',
+    Description       varchar(255)    NOT NULL DEFAULT '',
+    Disabled          integer         NOT NULL DEFAULT 0,
+    Creator           integer         NOT NULL DEFAULT 0,
+    Created           timestamp                DEFAULT NULL,
+    LastUpdatedBy     integer         NOT NULL DEFAULT 0,
+    LastUpdated       timestamp                DEFAULT NULL,
+    PRIMARY KEY (id)
+);
+
+CREATE INDEX RTxCatalogsName ON RTxCatalogs (LOWER(Name));
+CREATE INDEX RTxCatalogsDisabled ON RTxCatalogs (Disabled);
diff --git a/etc/schema.SQLite b/etc/schema.SQLite
index c50e5b1..cdd4d4b 100644
--- a/etc/schema.SQLite
+++ b/etc/schema.SQLite
@@ -519,3 +519,33 @@ Created TIMESTAMP NULL,
 LastUpdatedBy integer NOT NULL DEFAULT 0,
 LastUpdated TIMESTAMP NULL
 );
+CREATE TABLE RTxAssets (
+    id                INTEGER PRIMARY KEY,
+    Name              varchar(255)    NOT NULL DEFAULT '',
+    Catalog           int(11)         NOT NULL DEFAULT 0,
+    Status            varchar(64)     NOT NULL DEFAULT '',
+    Description       varchar(255)    NOT NULL DEFAULT '',
+    Creator           int(11)         NOT NULL DEFAULT 0,
+    Created           timestamp                DEFAULT NULL,
+    LastUpdatedBy     int(11)         NOT NULL DEFAULT 0,
+    LastUpdated       timestamp                DEFAULT NULL
+);
+
+CREATE INDEX RTxAssetsName on RTxAssets (Name);
+CREATE INDEX RTxAssetsStatus ON RTxAssets (Status);
+CREATE INDEX RTxAssetsCatalog ON RTxAssets (Catalog);
+
+CREATE TABLE RTxCatalogs (
+    id                INTEGER PRIMARY KEY,
+    Name              varchar(255)    NOT NULL DEFAULT '',
+    Lifecycle         varchar(32)     NOT NULL DEFAULT 'assets',
+    Description       varchar(255)    NOT NULL DEFAULT '',
+    Disabled          int2            NOT NULL DEFAULT 0,
+    Creator           int(11)         NOT NULL DEFAULT 0,
+    Created           timestamp                DEFAULT NULL,
+    LastUpdatedBy     int(11)         NOT NULL DEFAULT 0,
+    LastUpdated       timestamp                DEFAULT NULL
+);
+
+CREATE INDEX RTxCatalogsName on RTxCatalogs (Name);
+CREATE INDEX RTxCatalogsDisabled ON RTxCatalogs (Disabled);
diff --git a/etc/schema.mysql b/etc/schema.mysql
index da14e72..9949fb2 100644
--- a/etc/schema.mysql
+++ b/etc/schema.mysql
@@ -508,3 +508,35 @@ CREATE TABLE ObjectClasses (
   LastUpdated datetime default NULL,
   PRIMARY KEY  (id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE RTxAssets (
+    id                int(11)         NOT NULL AUTO_INCREMENT,
+    Name              varchar(255)    NOT NULL DEFAULT '',
+    Catalog           int(11)         NOT NULL DEFAULT 0,
+    Status            varchar(64)     NOT NULL DEFAULT '',
+    Description       varchar(255)    NOT NULL DEFAULT '',
+    Creator           int(11)         NOT NULL DEFAULT 0,
+    Created           datetime                 DEFAULT NULL,
+    LastUpdatedBy     int(11)         NOT NULL DEFAULT 0,
+    LastUpdated       datetime                 DEFAULT NULL,
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE INDEX RTxAssetsName ON RTxAssets (Name);
+CREATE INDEX RTxAssetsStatus ON RTxAssets (Status);
+CREATE INDEX RTxAssetsCatalog ON RTxAssets (Catalog);
+
+CREATE TABLE RTxCatalogs (
+    id                int(11)         NOT NULL AUTO_INCREMENT,
+    Name              varchar(255)    NOT NULL DEFAULT '',
+    Lifecycle         varchar(32)     NOT NULL DEFAULT 'assets',
+    Description       varchar(255)    NOT NULL DEFAULT '',
+    Disabled          int2            NOT NULL DEFAULT 0,
+    Creator           int(11)         NOT NULL DEFAULT 0,
+    Created           datetime                 DEFAULT NULL,
+    LastUpdatedBy     int(11)         NOT NULL DEFAULT 0,
+    LastUpdated       datetime                 DEFAULT NULL,
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE INDEX RTxCatalogsName ON RTxCatalogs (Name);
+CREATE INDEX RTxCatalogsDisabled ON RTxCatalogs (Disabled);

commit 0f24385d6628e0a6df6533ab2bd9269bf9975c52
Author: Todd Wade <todd at bestpractical.com>
Date:   Thu Jul 23 17:32:26 2015 -0400

    assets tests from rt-extension-assets
    
    all tests except the web tests are currenly passing

diff --git a/t/assets/api.t b/t/assets/api.t
index a36d8a8..df64eab 100644
--- a/t/assets/api.t
+++ b/t/assets/api.t
@@ -1,8 +1,7 @@
 use strict;
 use warnings;
 
-use lib 'xt/lib';
-use RT::Extension::Assets::Test tests => undef;
+use RT::Test::Assets tests => undef;
 use Test::Warn;
 
 my $catalog;
diff --git a/t/assets/collection.t b/t/assets/collection.t
index 7bf5911..f2c3019 100644
--- a/t/assets/collection.t
+++ b/t/assets/collection.t
@@ -1,8 +1,7 @@
 use strict;
 use warnings;
 
-use lib 'xt/lib';
-use RT::Extension::Assets::Test tests => undef;
+use RT::Test::Assets tests => undef;
 
 my $user = RT::Test->load_or_create_user( Name => 'testuser' );
 ok $user->id, "Created user";
diff --git a/t/assets/compile.t b/t/assets/compile.t
index 4ab1d21..ea27bc5 100644
--- a/t/assets/compile.t
+++ b/t/assets/compile.t
@@ -2,9 +2,8 @@ use strict;
 use warnings;
 
 use Test::More;
-use lib 'xt/lib';
 
-use_ok('RT::Extension::Assets::Test');
+use_ok('RT::Test::Assets');
 use_ok('RT::Asset');
 use_ok('RT::Assets');
 use_ok('RT::Catalog');
diff --git a/t/assets/links.t b/t/assets/links.t
index d81fdcc..a9101fe 100644
--- a/t/assets/links.t
+++ b/t/assets/links.t
@@ -1,8 +1,7 @@
 use strict;
 use warnings;
 
-use lib 'xt/lib';
-use RT::Extension::Assets::Test tests => undef;
+use RT::Test::Assets tests => undef;
 use Test::Warn;
 
 my $catalog = create_catalog( Name => "BPS" );
diff --git a/t/assets/rights.t b/t/assets/rights.t
index 30394b3..b28b16b 100644
--- a/t/assets/rights.t
+++ b/t/assets/rights.t
@@ -1,8 +1,7 @@
 use strict;
 use warnings;
 
-use lib 'xt/lib';
-use RT::Extension::Assets::Test tests => undef;
+use RT::Test::Assets tests => undef;
 
 my $user = RT::Test->load_or_create_user( Name => 'testuser' );
 ok $user->id, "Created user";
diff --git a/t/assets/roles.t b/t/assets/roles.t
index ffaa5d2..1d8a647 100644
--- a/t/assets/roles.t
+++ b/t/assets/roles.t
@@ -1,8 +1,7 @@
 use strict;
 use warnings;
 
-use lib 'xt/lib';
-use RT::Extension::Assets::Test tests => undef;
+use RT::Test::Assets tests => undef;
 
 my $catalog = create_catalog( Name => "A catalog" );
 my $asset = create_asset( Name => "Test asset", Catalog => $catalog->id );
diff --git a/t/assets/web.t b/t/assets/web.t
index 5579702..3595e26 100644
--- a/t/assets/web.t
+++ b/t/assets/web.t
@@ -1,8 +1,7 @@
 use strict;
 use warnings;
 
-use lib 'xt/lib';
-use RT::Extension::Assets::Test tests => undef;
+use RT::Test::Assets tests => undef;
 
 RT->Config->Set("CustomFieldGroupings",
     "RT::Asset" => {
@@ -28,7 +27,7 @@ my %CF = (
     Purchased   => ".CF-" . $purchased->id . "-Edit",
 );
 
-my ($base, $m) = RT::Extension::Assets::Test->started_ok;
+my ($base, $m) = RT::Test::Assets->started_ok;
 ok $m->login, "Logged in agent";
 
 diag "Create basic asset (no CFs)";

-----------------------------------------------------------------------


More information about the rt-commit mailing list