[Rt-commit] rt branch, 4.4/serialize-json-initialdata, updated. rt-4.4.1-354-gc6e0169

Shawn Moore shawn at bestpractical.com
Mon Mar 20 16:57:37 EDT 2017


The branch, 4.4/serialize-json-initialdata has been updated
       via  c6e016974d8df964d3815271f4ea605e3b63add8 (commit)
       via  a90d18d3267a0c9361dd06c0bf11fd8ea74e73c0 (commit)
       via  7c86a84a029f1ba4431d142a07049326b8638ed0 (commit)
       via  3a9545fa25f9d10e504ee26f272382359931bf8b (commit)
       via  ded0d923b6f8ef6a2cead7fa5990ca54c6351010 (commit)
       via  3f6721c135c0a6d0e6703a9e24e39c266daf0680 (commit)
       via  3abcb9e007949ae602caaa71f89d5500af0e70e1 (commit)
       via  60c3125fd382d45127f49c1118d6e3a0817d6256 (commit)
       via  ccb982d74e6e859b1e1c285e71864080659d264d (commit)
       via  0bbf2d740166bf1f4995afbc31abc6629f219aba (commit)
       via  7dfb677fcf6b7771e6dddb7127cd270a76a560e0 (commit)
       via  4e281b8d9029d5cd4fe42d92f9b1a035e1ed8e98 (commit)
       via  cf6b45ab86ca00320ae746b3a8dd2556f433ece9 (commit)
       via  256334a7f7440fc2372c172b4e65a3effbcb988c (commit)
       via  8037d969bc5507173c30791456b01465d0067e95 (commit)
       via  24fea90c0b57991a64e1a1e482e5932a23d4ef1a (commit)
       via  96382aeb8c5af06ead18236bb0ead3db31798c94 (commit)
      from  2dc725b4dce35d1daa6ca604af5c9d9943809af0 (commit)

Summary of changes:
 etc/RT_Config.pm.in               |  34 ++++++++
 lib/RT/Group.pm                   |  43 ++++++++--
 lib/RT/Handle.pm                  | 101 +++++++++++++++++++----
 lib/RT/Migrate/Serializer/JSON.pm |  99 +++++++++++++++++++++-
 t/api/initialdata-roundtrip.t     | 167 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 416 insertions(+), 28 deletions(-)
 create mode 100644 t/api/initialdata-roundtrip.t

- Log -----------------------------------------------------------------
commit 24fea90c0b57991a64e1a1e482e5932a23d4ef1a
Merge: 2dc725b 96382ae
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 17:11:58 2017 +0000

    Merge branch '4.4.1/json-initialdata' into 4.4/serialize-json-initialdata


commit 8037d969bc5507173c30791456b01465d0067e95
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 17:37:13 2017 +0000

    Only serialize user-defined groups for initialdata
    
    ACLEquivalence, role groups, etc will be created automatically

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 7e6d73d..2c48e05 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -120,6 +120,23 @@ sub Directory {
     return $self->{Directory};
 }
 
+sub Observe {
+    my $self = shift;
+    my %args = @_;
+
+    my $obj = $args{object};
+
+    # avoid serializing ACLEquivalence, etc
+    if ($obj->isa("RT::Group")) {
+        return 0 unless $obj->Domain eq 'UserDefined';
+    }
+    if ($obj->isa("RT::GroupMember")) {
+        return 0 unless $obj->GroupObj->Object->Domain eq 'UserDefined';
+    }
+
+    return $self->SUPER::Observe(%args);
+}
+
 sub JSON {
     my $self = shift;
     return $self->{JSON} ||= JSON->new->pretty->canonical;

commit 256334a7f7440fc2372c172b4e65a3effbcb988c
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 17:57:34 2017 +0000

    Avoid serializing attributes (for now)

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 2c48e05..d800284 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -274,6 +274,8 @@ sub WriteFile {
 
     $self->CanonicalizeObjects;
 
+    delete $self->{Records}{'RT::Attribute'};
+
     for my $intype (keys %{ $self->{Records} }) {
         my $outtype = $intype;
         $outtype =~ s/^RT:://;

commit cf6b45ab86ca00320ae746b3a8dd2556f433ece9
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:04:54 2017 +0000

    Handle explicit ApplyTo => 0 to make a CF which is global

diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index 0b62526..41aee32 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -1208,22 +1208,25 @@ sub InsertData {
 
             my $class = $new_entry->RecordClassFromLookupType;
             if ($class) {
-                if ($new_entry->IsOnlyGlobal and $apply_to) {
-                    $RT::Logger->warn("ApplyTo provided for global custom field ".$new_entry->Name );
-                    undef $apply_to;
-                }
-                if ( !$apply_to ) {
-                    # Apply to all by default
+                $apply_to = [ $apply_to ] unless ref $apply_to;
+                for my $name ( @{ $apply_to } ) {
                     my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
-                    ( $return, $msg) = $ocf->Create( CustomField => $new_entry->Id );
-                    $RT::Logger->error( $msg ) unless $return and $ocf->Id;
-                } else {
-                    $apply_to = [ $apply_to ] unless ref $apply_to;
-                    for my $name ( @{ $apply_to } ) {
+
+                    # global CF
+                    if (!$name) {
+                        ( $return, $msg ) = $ocf->Create(
+                            CustomField => $new_entry->Id,
+                        );
+                        $RT::Logger->error( $msg ) unless $return and $ocf->Id;
+                    }
+                    else {
+                        if ($new_entry->IsOnlyGlobal) {
+                            $RT::Logger->warn("ApplyTo '$name' provided for global custom field ".$new_entry->Name );
+                        }
+
                         my $obj = $class->new(RT->SystemUser);
                         $obj->Load($name);
                         if ( $obj->Id ) {
-                            my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
                             ( $return, $msg ) = $ocf->Create(
                                 CustomField => $new_entry->Id,
                                 ObjectId    => $obj->Id,

commit 4e281b8d9029d5cd4fe42d92f9b1a035e1ed8e98
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:23:49 2017 +0000

    Have _GetRecordByRef dereference if needed

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index d800284..944c225 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -179,6 +179,7 @@ sub _GetRecordByRef {
     my $self = shift;
     my $ref  = shift;
 
+    $ref = $$ref if ref($ref) eq 'SCALAR';
     my ($class) = $ref =~ /^([\w:]+)-/
         or return undef;
     return $self->{Records}{$class}{$ref};
@@ -249,7 +250,7 @@ sub CanonicalizeObjects {
         primary_class      => 'RT::CustomRole',
         canonicalize_object => sub {
             ref($_->{ObjectId})
-                ? $self->_GetRecordByRef(${ $_->{ObjectId} })->{Name}
+                ? $self->_GetRecordByRef($_->{ObjectId})->{Name}
                 : $_->{ObjectId};
         },
     );
@@ -261,7 +262,7 @@ sub CanonicalizeObjects {
         primary_key        => 'Queue',
         canonicalize_object => sub {
             my $object = ref($_->{ObjectId})
-                ? $self->_GetRecordByRef(${ $_->{ObjectId} })->{Name}
+                ? $self->_GetRecordByRef($_->{ObjectId})->{Name}
                 : $_->{ObjectId};
             return { ObjectId => $object, Stage => $_->{Stage} };
         },

commit 7dfb677fcf6b7771e6dddb7127cd270a76a560e0
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:26:10 2017 +0000

    Try harder to load references
    
    Even if it's not in the serialized output (perhaps due to being
    excluded) we can still inspect such objects because we have their class
    and id, and the database

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 944c225..71fa923 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -180,9 +180,17 @@ sub _GetRecordByRef {
     my $ref  = shift;
 
     $ref = $$ref if ref($ref) eq 'SCALAR';
-    my ($class) = $ref =~ /^([\w:]+)-/
+
+    return RT->System if $ref eq 'RT::System';
+
+    my ($class, $id) = $ref =~ /^([\w:]+)-.*-(\d+)$/
         or return undef;
-    return $self->{Records}{$class}{$ref};
+
+    return $self->{Records}{$class}{$ref} || do {
+        my $obj = $class->new(RT->SystemUser);
+        $obj->Load($id);
+        $obj;
+    };
 }
 
 sub CanonicalizeReference {

commit 0bbf2d740166bf1f4995afbc31abc6629f219aba
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:33:16 2017 +0000

    Handle RightName in initialdata
    
    This is what RT::ACE calls it

diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index 41aee32..1390507 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -1346,6 +1346,8 @@ sub InsertData {
                 }
             }
 
+            $item->{Right} = delete $item->{RightName} if $item->{RightName};
+
             # Grant it
             my @rights = ref($item->{'Right'}) eq 'ARRAY' ? @{$item->{'Right'}} : $item->{'Right'};
             foreach my $right ( @rights ) {

commit ccb982d74e6e859b1e1c285e71864080659d264d
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:33:32 2017 +0000

    Canonicalize ACLs the way initialdata needs them

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 71fa923..5d8a7b2 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -237,6 +237,37 @@ sub _CanonicalizeManyToMany {
     }
 }
 
+sub CanonicalizeACLs {
+    my $self = shift;
+
+    for my $ace (values %{ $self->{Records}{'RT::ACE'} }) {
+        my $principal = $self->_GetRecordByRef(delete $ace->{PrincipalId});
+        my $object = $self->_GetRecordByRef(delete $ace->{Object});
+
+        if ($principal->IsGroup) {
+            my $domain = $principal->Object->Domain;
+            if ($domain eq 'ACLEquivalence') {
+                $ace->{UserId} = $principal->Object->InstanceObj->Name;
+            }
+            else {
+                $ace->{GroupDomain} = $domain;
+                if ($domain eq 'SystemInternal') {
+                    $ace->{GroupType} = $principal->Object->Name;
+                }
+                elsif ($domain eq 'RT::Queue-Role') {
+                    $ace->{Queue} = $principal->Object->Instance;
+                }
+            }
+        }
+        else {
+            $ace->{UserId} = $principal->Object->Name;
+        }
+
+        $ace->{ObjectType} = ref($object);
+        $ace->{ObjectId} = $object->Id;
+    }
+}
+
 sub CanonicalizeObjects {
     my $self = shift;
 
@@ -282,6 +313,7 @@ sub WriteFile {
     my %output;
 
     $self->CanonicalizeObjects;
+    $self->CanonicalizeACLs;
 
     delete $self->{Records}{'RT::Attribute'};
 

commit 60c3125fd382d45127f49c1118d6e3a0817d6256
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:42:07 2017 +0000

    Disambiguate _GetSerializedByRef from _GetObjectByRef
    
    The consuming API is different (serialized hashref vs live object)

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 5d8a7b2..11272a3 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -175,7 +175,7 @@ my %initialdataType = (
     GroupMember => 'Members',
 );
 
-sub _GetRecordByRef {
+sub _GetObjectByRef {
     my $self = shift;
     my $ref  = shift;
 
@@ -186,11 +186,21 @@ sub _GetRecordByRef {
     my ($class, $id) = $ref =~ /^([\w:]+)-.*-(\d+)$/
         or return undef;
 
-    return $self->{Records}{$class}{$ref} || do {
-        my $obj = $class->new(RT->SystemUser);
-        $obj->Load($id);
-        $obj;
-    };
+    my $obj = $class->new(RT->SystemUser);
+    $obj->Load($id);
+    return $obj;
+}
+
+sub _GetSerializedByRef {
+    my $self = shift;
+    my $ref  = shift;
+
+    $ref = $$ref if ref($ref) eq 'SCALAR';
+
+    my ($class) = $ref =~ /^([\w:]+)-/
+        or return undef;
+
+    return $self->{Records}{$class}{$ref};
 }
 
 sub CanonicalizeReference {
@@ -199,7 +209,7 @@ sub CanonicalizeReference {
     my $context = shift;
     my $for_key = shift;
 
-    my $record = $self->_GetRecordByRef($ref)
+    my $record = $self->_GetSerializedByRef($ref)
         or return $ref;
 
     return $record->{Name} || $ref;
@@ -241,8 +251,8 @@ sub CanonicalizeACLs {
     my $self = shift;
 
     for my $ace (values %{ $self->{Records}{'RT::ACE'} }) {
-        my $principal = $self->_GetRecordByRef(delete $ace->{PrincipalId});
-        my $object = $self->_GetRecordByRef(delete $ace->{Object});
+        my $principal = $self->_GetObjectByRef(delete $ace->{PrincipalId});
+        my $object = $self->_GetObjectByRef(delete $ace->{Object});
 
         if ($principal->IsGroup) {
             my $domain = $principal->Object->Domain;
@@ -289,7 +299,7 @@ sub CanonicalizeObjects {
         primary_class      => 'RT::CustomRole',
         canonicalize_object => sub {
             ref($_->{ObjectId})
-                ? $self->_GetRecordByRef($_->{ObjectId})->{Name}
+                ? $self->_GetSerializedByRef($_->{ObjectId})->{Name}
                 : $_->{ObjectId};
         },
     );
@@ -301,7 +311,7 @@ sub CanonicalizeObjects {
         primary_key        => 'Queue',
         canonicalize_object => sub {
             my $object = ref($_->{ObjectId})
-                ? $self->_GetRecordByRef($_->{ObjectId})->{Name}
+                ? $self->_GetSerializedByRef($_->{ObjectId})->{Name}
                 : $_->{ObjectId};
             return { ObjectId => $object, Stage => $_->{Stage} };
         },

commit 3abcb9e007949ae602caaa71f89d5500af0e70e1
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:47:28 2017 +0000

    Handle systemuser

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 11272a3..d8efbec 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -182,9 +182,10 @@ sub _GetObjectByRef {
     $ref = $$ref if ref($ref) eq 'SCALAR';
 
     return RT->System if $ref eq 'RT::System';
+    return RT->SystemUser if $ref eq 'RT::User-RT_System';
 
     my ($class, $id) = $ref =~ /^([\w:]+)-.*-(\d+)$/
-        or return undef;
+        or do { warn "Unable to canonicalize ref '$ref'"; return undef };
 
     my $obj = $class->new(RT->SystemUser);
     $obj->Load($id);

commit 3f6721c135c0a6d0e6703a9e24e39c266daf0680
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:47:34 2017 +0000

    Creator and LastUpdatedBy are always user ids

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index d8efbec..22dd12f 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -210,6 +210,10 @@ sub CanonicalizeReference {
     my $context = shift;
     my $for_key = shift;
 
+    if ($for_key eq 'Creator' || $for_key eq 'LastUpdatedBy') {
+        return $self->_GetObjectByRef($ref)->Id;
+    }
+
     my $record = $self->_GetSerializedByRef($ref)
         or return $ref;
 

commit ded0d923b6f8ef6a2cead7fa5990ca54c6351010
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:47:53 2017 +0000

    Avoid uniqueness violations (for now) by skipping id from initialdata output
    
    This will likely be added in to handle updating existing records

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 22dd12f..1f63c9c 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -344,6 +344,7 @@ sub WriteFile {
                     $record->{$key} = $self->CanonicalizeReference($record->{$key}, $record, $key);
                 }
             }
+            delete $record->{id};
             push @{ $output{$outtype} }, $record;
         }
     }

commit 3a9545fa25f9d10e504ee26f272382359931bf8b
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 18:53:01 2017 +0000

    Handle passing a hash with Stage as first param to Queue

diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index 1390507..2011ebd 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -1424,7 +1424,18 @@ sub InsertData {
             my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
             push @queues, 0 unless @queues; # add global queue at least
 
-            my ( $return, $msg ) = $new_entry->Create( %$item, Queue => shift @queues );
+            my %args = %$item;
+            $args{Queue} = shift @queues;
+            if (ref($args{Queue})) {
+                # transform ScripObject->Create API into Scrip->Create API
+                $args{Queue}{Queue} = delete $args{Queue}{ObjectId};
+                %args = (
+                    %args,
+                    %{ $args{Queue} },
+                );
+            }
+
+            my ( $return, $msg ) = $new_entry->Create(%args);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
                 next;

commit 7c86a84a029f1ba4431d142a07049326b8638ed0
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 19:01:41 2017 +0000

    CanonicalizeUsers
    
    Skip principal fields, add Privileged

diff --git a/lib/RT/Migrate/Serializer/JSON.pm b/lib/RT/Migrate/Serializer/JSON.pm
index 1f63c9c..dd35607 100644
--- a/lib/RT/Migrate/Serializer/JSON.pm
+++ b/lib/RT/Migrate/Serializer/JSON.pm
@@ -283,6 +283,20 @@ sub CanonicalizeACLs {
     }
 }
 
+sub CanonicalizeUsers {
+    my $self = shift;
+
+    for my $user (values %{ $self->{Records}{'RT::User'} }) {
+        delete $user->{Principal};
+        delete $user->{PrincipalId};
+
+        my $object = RT::User->new(RT->SystemUser);
+        $object->Load($user->{id});
+
+        $user->{Privileged} = $object->Privileged ? JSON::true : JSON::false;
+    }
+}
+
 sub CanonicalizeObjects {
     my $self = shift;
 
@@ -329,6 +343,7 @@ sub WriteFile {
 
     $self->CanonicalizeObjects;
     $self->CanonicalizeACLs;
+    $self->CanonicalizeUsers;
 
     delete $self->{Records}{'RT::Attribute'};
 

commit a90d18d3267a0c9361dd06c0bf11fd8ea74e73c0
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 19:26:43 2017 +0000

    Avoid creating duplicate SystemInternal and RT::Role-System groups
    
    We've seen copies of the SystemInternal groups in the wild, which causes
    symptoms such as privileged users being forced into self-service
    
    initialdata is one avenue by which this could happen, as there was no
    validation previously

diff --git a/lib/RT/Group.pm b/lib/RT/Group.pm
index e6fe7dd..1593700 100644
--- a/lib/RT/Group.pm
+++ b/lib/RT/Group.pm
@@ -305,10 +305,18 @@ sub _Create {
         @_
     );
 
-    # Enforce uniqueness on user defined group names
-    if ($args{'Domain'} and $args{'Domain'} eq 'UserDefined') {
-        my ($ok, $msg) = $self->_ValidateUserDefinedName($args{'Name'});
-        return ($ok, $msg) if not $ok;
+    if ($args{'Domain'}) {
+        # Enforce uniqueness on user defined group names
+        if ($args{'Domain'} eq 'UserDefined') {
+            my ($ok, $msg) = $self->_ValidateUserDefinedName($args{'Name'});
+            return ($ok, $msg) if not $ok;
+        }
+
+        # Enforce uniqueness on SystemInternal and system role groups
+        if ($args{'Domain'} eq 'SystemInternal' || $args{'Domain'} eq 'RT::System-Role') {
+            my ($ok, $msg) = $self->_ValidateNameForDomain($args{'Name'}, $args{'Domain'});
+            return ($ok, $msg) if not $ok;
+        }
     }
 
     $RT::Handle->BeginTransaction() unless ($args{'InsideTransaction'});
@@ -398,25 +406,42 @@ sub ValidateName {
     return $self->SUPER::ValidateName($value);
 }
 
-=head2 _ValidateUserDefinedName VALUE
+=head2 _ValidateNameForDomain VALUE DOMAIN
 
-Returns true if the user defined group name isn't in use, false otherwise.
+Returns true if the group name isn't in use in the same domain, false otherwise.
 
 =cut
 
-sub _ValidateUserDefinedName {
-    my ($self, $value) = @_;
+sub _ValidateNameForDomain {
+    my ($self, $value, $domain) = @_;
 
     return (0, 'Name is required') unless length $value;
 
     my $dupcheck = RT::Group->new(RT->SystemUser);
-    $dupcheck->LoadUserDefinedGroup($value);
+    if ($domain eq 'UserDefined') {
+        $dupcheck->LoadUserDefinedGroup($value);
+    }
+    else {
+        $dupcheck->LoadByCols(Domain => $domain, Name => $value);
+    }
     if ( $dupcheck->id && ( !$self->id || $self->id != $dupcheck->id ) ) {
         return ( 0, $self->loc( "Group name '[_1]' is already in use", $value ) );
     }
     return 1;
 }
 
+=head2 _ValidateUserDefinedName VALUE
+
+Returns true if the user defined group name isn't in use, false otherwise.
+
+=cut
+
+sub _ValidateUserDefinedName {
+    my ($self, $value) = @_;
+
+    return $self->_ValidateNameForDomain($value, 'UserDefined');
+}
+
 =head2 _CreateACLEquivalenceGroup { Principal }
 
 A helper subroutine which creates a group containing only 

commit c6e016974d8df964d3815271f4ea605e3b63add8
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Mon Mar 20 20:56:10 2017 +0000

    First cut of initialdata roundtrip tests
    
    This relies on an extension inside RT, which is obviously unsuitable.
    But during development it's the best place for tests

diff --git a/t/api/initialdata-roundtrip.t b/t/api/initialdata-roundtrip.t
new file mode 100644
index 0000000..83e387c
--- /dev/null
+++ b/t/api/initialdata-roundtrip.t
@@ -0,0 +1,167 @@
+use strict;
+use warnings;
+
+use RT::Test tests => undef, config => << 'CONFIG';
+Plugin('RT::Extension::Initialdata::JSON');
+Set($InitialdataFormatHandlers, [ 'perl', 'RT::Extension::Initialdata::JSON' ]);
+CONFIG
+
+my @tests = (
+    {
+        name => 'Simple user-defined group',
+        create => sub {
+            my $group = RT::Group->new(RT->SystemUser);
+            my ($ok, $msg) = $group->CreateUserDefinedGroup(Name => 'Staff');
+            ok($ok, $msg);
+        },
+        absent => sub {
+            my $group = RT::Group->new(RT->SystemUser);
+            $group->LoadUserDefinedGroup('Staff');
+            ok(!$group->Id, 'No such group');
+        },
+        present => sub {
+            my $group = RT::Group->new(RT->SystemUser);
+            $group->LoadUserDefinedGroup('Staff');
+            ok($group->Id, 'Loaded group');
+            is($group->Name, 'Staff', 'Group name');
+            is($group->Domain, 'UserDefined', 'Domain');
+        },
+    },
+
+    {
+        name => 'Custom field on two queues',
+        create => sub {
+            my $bugs = RT::Queue->new(RT->SystemUser);
+            my ($ok, $msg) = $bugs->Create(Name => 'Bugs');
+            ok($ok, $msg);
+
+            my $features = RT::Queue->new(RT->SystemUser);
+            ($ok, $msg) = $features->Create(Name => 'Features');
+            ok($ok, $msg);
+
+            my $cf = RT::CustomField->new(RT->SystemUser);
+            ($ok, $msg) = $cf->Create(
+                Name => 'Fixed In',
+                Type => 'SelectSingle',
+                LookupType => RT::Queue->CustomFieldLookupType,
+            );
+            ok($ok, $msg);
+
+            ($ok, $msg) = $cf->AddToObject($bugs);
+            ok($ok, $msg);
+
+            ($ok, $msg) = $cf->AddToObject($features);
+            ok($ok, $msg);
+        },
+        present => sub {
+            my $bugs = RT::Queue->new(RT->SystemUser);
+            $bugs->Load('Bugs');
+            ok($bugs->Id, 'Bugs queue loaded');
+            is($bugs->Name, 'Bugs');
+
+            my $features = RT::Queue->new(RT->SystemUser);
+            $features->Load('Features');
+            ok($features->Id, 'Features queue loaded');
+            is($features->Name, 'Features');
+
+            my $cf = RT::CustomField->new(RT->SystemUser);
+            $cf->Load('Fixed In');
+            ok($cf->Id, 'Features queue loaded');
+            is($cf->Name, 'Fixed In');
+            is($cf->Type, 'Select', 'Type');
+            is($cf->MaxValues, 1, 'MaxValues');
+            is($cf->LookupType, RT::Queue->CustomFieldLookupType, 'LookupType');
+        },
+    },
+);
+
+my $id = 0;
+for my $test (@tests) {
+    $id++;
+    my $directory = File::Spec->catdir(RT::Test->temp_directory, "export-$id");
+
+    # we get a lot of warnings about already-existing objects; suppress them
+    # for now until we clean it up
+    my $warn = $SIG{__WARN__};
+    local $SIG{__WARN__} = sub {
+        return if $_[0] =~ join '|', (
+            qr/^Name in use$/,
+            qr/^Group name '.*' is already in use$/,
+            qr/^A Template with that name already exists$/,
+            qr/^.* already has the right .* on .*$/,
+            qr/^Invalid value for Name$/,
+            qr/^Queue already exists$/,
+            qr/^Use of uninitialized value in/,
+        );
+
+        # Avoid reporting this anonymous call frame as the source of the warning
+        goto &$warn;
+    };
+
+    subtest "$test->{name} (ordinary creation)" => sub {
+        autorollback(sub {
+            $test->{absent}->() if $test->{absent};
+            $test->{create}->();
+            $test->{present}->() if $test->{present};
+            export_initialdata($directory);
+        });
+    };
+
+    subtest "$test->{name} (from initialdata)" => sub {
+        autorollback(sub {
+            $test->{absent}->() if $test->{absent};
+            import_initialdata($directory);
+            $test->{present}->() if $test->{present};
+        });
+    };
+}
+
+done_testing();
+
+# vvvv   here be dragons   vvvv
+
+sub autorollback {
+    my $code = shift;
+
+    $RT::Handle->BeginTransaction;
+    {
+        # avoid "Rollback and commit are mixed while escaping nested transaction" warnings
+        # due to (begin; (begin; commit); rollback)
+        no warnings 'redefine';
+        local *DBIx::SearchBuilder::Handle::BeginTransaction = sub {};
+        local *DBIx::SearchBuilder::Handle::Commit = sub {};
+        local *DBIx::SearchBuilder::Handle::Rollback = sub {};
+
+        $code->();
+    }
+    $RT::Handle->Rollback;
+}
+
+sub export_initialdata {
+    my $directory = shift;
+    local @RT::Record::ISA = qw( DBIx::SearchBuilder::Record RT::Base );
+
+    use RT::Migrate::Serializer::JSON;
+    my $migrator = RT::Migrate::Serializer::JSON->new(
+        Directory          => $directory,
+        Verbose            => 0,
+        AllUsers           => 0,
+        FollowACL          => 1,
+        FollowScrips       => 1,
+        FollowTransactions => 0,
+    );
+
+    $migrator->Export;
+}
+
+sub import_initialdata {
+    my $directory = shift;
+    my $initialdata = File::Spec->catfile($directory, "initialdata.json");
+
+    ok(-e $initialdata, "File $initialdata exists");
+
+    my ($rv, $msg) = RT->DatabaseHandle->InsertData( $initialdata, undef, disconnect_after => 0 );
+    ok($rv, "Inserted test data from $initialdata")
+        or diag "Error: $msg";
+}
+

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


More information about the rt-commit mailing list