Bug 17268: Advanced cataloging editor macros - add endpoint
authorNick Clemens <nick@bywatersolutions.com>
Wed, 4 Dec 2019 11:39:41 +0000 (11:39 +0000)
committerMartin Renvoize <martin.renvoize@ptfs-europe.com>
Mon, 4 May 2020 07:25:21 +0000 (08:25 +0100)
Signed-off-by: Andrew Fuerste-Henry <andrew@bywatersolutions.com>
Signed-off-by: Heather Hernandez <Heather_Hernandez@nps.gov>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Koha/AdvancedEditorMacro.pm [new file with mode: 0644]
Koha/AdvancedEditorMacros.pm [new file with mode: 0644]
Koha/REST/V1/AdvancedEditorMacro.pm [new file with mode: 0644]
api/v1/swagger/definitions.json
api/v1/swagger/definitions/advancededitormacro.json [new file with mode: 0644]
api/v1/swagger/parameters.json
api/v1/swagger/parameters/advancededitormacro.json [new file with mode: 0644]
api/v1/swagger/paths.json
api/v1/swagger/paths/advancededitormacros.json [new file with mode: 0644]
api/v1/swagger/x-primitives.json
t/db_dependent/api/v1/advanced_editor_macros.t [new file with mode: 0644]

diff --git a/Koha/AdvancedEditorMacro.pm b/Koha/AdvancedEditorMacro.pm
new file mode 100644 (file)
index 0000000..d30fcc5
--- /dev/null
@@ -0,0 +1,57 @@
+package Koha::AdvancedEditorMacro;
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use base qw(Koha::Object);
+
+=head1 NAME
+
+Koha::AdvancedEditorMacro - Koha Advanced Editor Macro Object class
+
+=head1 API
+
+=head2 Class methods
+
+=head3 to_api_mapping
+
+=cut
+
+sub to_api_mapping {
+    return {
+        id                  => 'macro_id',
+        macro               => 'macro_text',
+        borrowernumber      => 'patron_id',
+    };
+}
+
+
+=head2 Internal methods
+
+=head3 _type
+
+=cut
+
+sub _type {
+    return 'AdvancedEditorMacro';
+}
+
+1;
diff --git a/Koha/AdvancedEditorMacros.pm b/Koha/AdvancedEditorMacros.pm
new file mode 100644 (file)
index 0000000..67d4428
--- /dev/null
@@ -0,0 +1,50 @@
+package Koha::AdvancedEditorMacros;
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Carp;
+
+use Koha::Database;
+
+use Koha::AdvancedEditorMacro;
+
+use base qw(Koha::Objects);
+
+=head1 NAME
+
+Koha::AdvancedEditorMacros - Koha Advanced Editor Macro Object set class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 type
+
+=cut
+
+sub _type {
+    return 'AdvancedEditorMacro';
+}
+
+sub object_class {
+    return 'Koha::AdvancedEditorMacro';
+}
+
+1;
diff --git a/Koha/REST/V1/AdvancedEditorMacro.pm b/Koha/REST/V1/AdvancedEditorMacro.pm
new file mode 100644 (file)
index 0000000..92f6730
--- /dev/null
@@ -0,0 +1,397 @@
+package Koha::REST::V1::AdvancedEditorMacro;
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Mojo::Base 'Mojolicious::Controller';
+use Koha::AdvancedEditorMacros;
+
+use Try::Tiny;
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head3 list
+
+Controller function that handles listing Koha::AdvancedEditorMacro objects
+
+=cut
+
+sub list {
+    my $c = shift->openapi->valid_input or return;
+    my $patron = $c->stash('koha.user');
+    return try {
+        my $macros_set = Koha::AdvancedEditorMacros->search({ -or => { shared => 1, borrowernumber => $patron->borrowernumber } });
+        my $macros = $c->objects->search( $macros_set, \&_to_model, \&_to_api );
+        return $c->render( status => 200, openapi => $macros );
+    }
+    catch {
+        if ( $_->isa('DBIx::Class::Exception') ) {
+            return $c->render( status  => 500,
+                               openapi => { error => $_->{msg} } );
+        }
+        else {
+            return $c->render( status => 500,
+                openapi => { error => "Something went wrong, check the logs. $_"} );
+        }
+    };
+
+}
+
+=head3 get
+
+Controller function that handles retrieving a single Koha::AdvancedEditorMacro
+
+=cut
+
+sub get {
+    my $c = shift->openapi->valid_input or return;
+    my $patron = $c->stash('koha.user');
+    my $macro = Koha::AdvancedEditorMacros->find({
+        id => $c->validation->param('advancededitormacro_id'),
+    });
+    unless ($macro) {
+        return $c->render( status  => 404,
+                           openapi => { error => "Macro not found" } );
+    }
+    if( $macro->shared ){
+        return $c->render( status => 403, openapi => {
+            error => "This macro is shared, you must access it via advancededitormacros/shared"
+        });
+    }
+    warn $macro->borrowernumber;
+    warn $patron->borrowernumber;
+    if( $macro->borrowernumber != $patron->borrowernumber ){
+        return $c->render( status => 403, openapi => {
+            error => "You do not have permission to access this macro"
+        });
+    }
+
+    return $c->render( status => 200, openapi => $macro->to_api );
+}
+
+=head3 get_shared
+
+Controller function that handles retrieving a single Koha::AdvancedEditorMacro
+
+=cut
+
+sub get_shared {
+    my $c = shift->openapi->valid_input or return;
+    my $patron = $c->stash('koha.user');
+    my $macro = Koha::AdvancedEditorMacros->find({
+        id => $c->validation->param('advancededitormacro_id'),
+    });
+    unless ($macro) {
+        return $c->render( status  => 404,
+                           openapi => { error => "Macro not found" } );
+    }
+    unless( $macro->shared ){
+        return $c->render( status => 403, openapi => {
+            error => "This macro is not shared, you must access it via advancededitormacros"
+        });
+    }
+    return $c->render( status => 200, openapi => $macro->to_api );
+}
+
+=head3 add
+
+Controller function that handles adding a new Koha::AdvancedEditorMacro object
+
+=cut
+
+sub add {
+    my $c = shift->openapi->valid_input or return;
+
+    if( defined $c->validation->param('body')->{shared} && $c->validation->param('body')->{shared} == 1 ){
+        return $c->render( status  => 403,
+                           openapi => { error => "To create shared macros you must use advancededitor/shared" } );
+    }
+
+    return try {
+        my $macro = Koha::AdvancedEditorMacro->new( _to_model( $c->validation->param('body') ) );
+        $macro->store;
+        $c->res->headers->location( $c->req->url->to_string . '/' . $macro->id );
+        return $c->render(
+            status  => 201,
+            openapi => $macro->to_api
+        );
+    }
+    catch { handle_error($_) };
+}
+
+=head3 add_shared
+
+Controller function that handles adding a new shared Koha::AdvancedEditorMacro object
+
+=cut
+
+sub add_shared {
+    my $c = shift->openapi->valid_input or return;
+
+    unless( defined $c->validation->param('body')->{shared} && $c->validation->param('body')->{shared} == 1 ){
+        return $c->render( status  => 403,
+                           openapi => { error => "To create private macros you must use advancededitor" } );
+    }
+
+    return try {
+        my $macro = Koha::AdvancedEditorMacro->new( _to_model( $c->validation->param('body') ) );
+        $macro->store;
+        $c->res->headers->location( $c->req->url->to_string . '/' . $macro->id );
+        return $c->render(
+            status  => 201,
+            openapi => $macro->to_api
+        );
+    }
+    catch { handle_error($_) };
+}
+
+=head3 update
+
+Controller function that handles updating a Koha::AdvancedEditorMacro object
+
+=cut
+
+sub update {
+    my $c = shift->openapi->valid_input or return;
+
+    my $macro = Koha::AdvancedEditorMacros->find( $c->validation->param('advancededitormacro_id') );
+
+    if ( not defined $macro ) {
+        return $c->render( status  => 404,
+                           openapi => { error => "Object not found" } );
+    }
+    my $patron = $c->stash('koha.user');
+
+    if( $macro->shared == 1 || defined $c->validation->param('body')->{shared} && $c->validation->param('body')->{shared} == 1 ){
+        return $c->render( status  => 403,
+                           openapi => { error => "To update a macro as shared you must use the advancededitormacros/shared endpoint" } );
+    } else {
+        unless ( $macro->borrowernumber == $patron->borrowernumber ){
+            return $c->render( status  => 403,
+                               openapi => { error => "You can only edit macros you own" } );
+        }
+    }
+
+    return try {
+        my $params = $c->req->json;
+        $macro->set( _to_model($params) );
+        $macro->store();
+        return $c->render( status => 200, openapi => $macro->to_api );
+    }
+    catch { handle_error($_) };
+}
+
+=head3 update_shared
+
+Controller function that handles updating a shared Koha::AdvancedEditorMacro object
+
+=cut
+
+sub update_shared {
+    my $c = shift->openapi->valid_input or return;
+
+    my $macro = Koha::AdvancedEditorMacros->find( $c->validation->param('advancededitormacro_id') );
+
+    if ( not defined $macro ) {
+        return $c->render( status  => 404,
+                           openapi => { error => "Object not found" } );
+    }
+
+    unless( $macro->shared == 1 || defined $c->validation->param('body')->{shared} && $c->validation->param('body')->{shared} == 1 ){
+        return $c->render( status  => 403,
+                           openapi => { error => "You can only update shared macros using this endpoint" } );
+    }
+
+    return try {
+        my $params = $c->req->json;
+        $macro->set( _to_model($params) );
+        $macro->store();
+        return $c->render( status => 200, openapi => $macro->to_api );
+    }
+    catch { handle_error($_) };
+}
+
+=head3 delete
+
+Controller function that handles deleting a Koha::AdvancedEditorMacro object
+
+=cut
+
+sub delete {
+    my $c = shift->openapi->valid_input or return;
+
+    my $macro = Koha::AdvancedEditorMacros->find( $c->validation->param('advancededitormacro_id') );
+    if ( not defined $macro ) {
+        return $c->render( status  => 404,
+                           openapi => { error => "Object not found" } );
+    }
+
+    my $patron = $c->stash('koha.user');
+    if( $macro->shared == 1 ){
+        return $c->render( status  => 403,
+                           openapi => { error => "You cannot delete shared macros using this endpoint" } );
+    } else {
+        unless ( $macro->borrowernumber == $patron->borrowernumber ){
+            return $c->render( status  => 403,
+                               openapi => { error => "You can only delete macros you own" } );
+        }
+    }
+
+    return try {
+        $macro->delete;
+        return $c->render( status => 200, openapi => "" );
+    }
+    catch { handle_error($_) };
+}
+
+=head3 delete_shared
+
+Controller function that handles deleting a shared Koha::AdvancedEditorMacro object
+
+=cut
+
+sub delete_shared {
+    my $c = shift->openapi->valid_input or return;
+
+    my $macro = Koha::AdvancedEditorMacros->find( $c->validation->param('advancededitormacro_id') );
+    if ( not defined $macro ) {
+        return $c->render( status  => 404,
+                           openapi => { error => "Object not found" } );
+    }
+
+    unless( $macro->shared == 1 ){
+        return $c->render( status  => 403,
+                           openapi => { error => "You can only delete shared macros using this endpoint" } );
+    }
+
+    return try {
+        $macro->delete;
+        return $c->render( status => 200, openapi => "" );
+    }
+    catch { handle_error($_,$c) };
+}
+
+=head3 _handle_error
+
+Helper function that passes exception or error
+
+=cut
+
+sub _handle_error {
+    my ($err,$c) = @_;
+    if ( $err->isa('DBIx::Class::Exception') ) {
+        return $c->render( status  => 500,
+                           openapi => { error => $err->{msg} } );
+    }
+    else {
+        return $c->render( status => 500,
+            openapi => { error => "Something went wrong, check the logs."} );
+    }
+};
+
+
+=head3 _to_api
+
+Helper function that maps a hashref of Koha::AdvancedEditorMacro attributes into REST api
+attribute names.
+
+=cut
+
+sub _to_api {
+    my $macro = shift;
+
+    # Rename attributes
+    foreach my $column ( keys %{ $Koha::REST::V1::AdvancedEditorMacro::to_api_mapping } ) {
+        my $mapped_column = $Koha::REST::V1::AdvancedEditorMacro::to_api_mapping->{$column};
+        if (    exists $macro->{ $column }
+             && defined $mapped_column )
+        {
+            # key /= undef
+            $macro->{ $mapped_column } = delete $macro->{ $column };
+        }
+        elsif (    exists $macro->{ $column }
+                && !defined $mapped_column )
+        {
+            # key == undef => to be deleted
+            delete $macro->{ $column };
+        }
+    }
+
+    return $macro;
+}
+
+=head3 _to_model
+
+Helper function that maps REST api objects into Koha::AdvancedEditorMacros
+attribute names.
+
+=cut
+
+sub _to_model {
+    my $macro = shift;
+
+    foreach my $attribute ( keys %{ $Koha::REST::V1::AdvancedEditorMacro::to_model_mapping } ) {
+        my $mapped_attribute = $Koha::REST::V1::AdvancedEditorMacro::to_model_mapping->{$attribute};
+        if (    exists $macro->{ $attribute }
+             && defined $mapped_attribute )
+        {
+            # key /= undef
+            $macro->{ $mapped_attribute } = delete $macro->{ $attribute };
+        }
+        elsif (    exists $macro->{ $attribute }
+                && !defined $mapped_attribute )
+        {
+            # key == undef => to be deleted
+            delete $macro->{ $attribute };
+        }
+    }
+
+    if ( exists $macro->{shared} ) {
+        $macro->{shared} = ($macro->{shared}) ? 1 : 0;
+    }
+
+
+    return $macro;
+}
+
+=head2 Global variables
+
+=head3 $to_api_mapping
+
+=cut
+
+our $to_api_mapping = {
+    id                  => 'macro_id',
+    macro               => 'macro_text',
+    borrowernumber      => 'patron_id',
+};
+
+=head3 $to_model_mapping
+
+=cut
+
+our $to_model_mapping = {
+    macro_id         => 'id',
+    macro_text       => 'macro',
+    patron_id        => 'borrowernumber',
+};
+
+1;
index 4b21d32..716e870 100644 (file)
@@ -47,6 +47,9 @@
   "order": {
     "$ref": "definitions/order.json"
   },
+  "advancededitormacro": {
+    "$ref": "definitions/advancededitormacro.json"
+  },
   "patron": {
     "$ref": "definitions/patron.json"
   },
diff --git a/api/v1/swagger/definitions/advancededitormacro.json b/api/v1/swagger/definitions/advancededitormacro.json
new file mode 100644 (file)
index 0000000..46e6eeb
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "type": "object",
+  "properties": {
+    "macro_id": {
+      "$ref": "../x-primitives.json#/advancededitormacro_id"
+    },
+    "name": {
+      "description": "macro name",
+      "type": "string"
+    },
+    "macro_text": {
+      "description": "macro text",
+      "type": ["string", "null"]
+    },
+    "patron_id": {
+      "description": "borrower number",
+      "type": ["integer", "null"]
+    },
+    "shared": {
+        "description": "is macro shared",
+        "type": ["boolean", "null"]
+    }
+  },
+  "additionalProperties": false,
+  "required": ["name", "macro_text", "patron_id", "shared"]
+}
index 9c3d7eb..cab2d53 100644 (file)
@@ -2,6 +2,9 @@
   "biblio_id_pp": {
     "$ref": "parameters/biblio.json#/biblio_id_pp"
   },
+  "advancededitormacro_id_pp": {
+    "$ref": "parameters/advancededitormacro.json#/advancededitormacro_id_pp"
+  },
   "patron_id_pp": {
     "$ref": "parameters/patron.json#/patron_id_pp"
   },
diff --git a/api/v1/swagger/parameters/advancededitormacro.json b/api/v1/swagger/parameters/advancededitormacro.json
new file mode 100644 (file)
index 0000000..482e0bc
--- /dev/null
@@ -0,0 +1,9 @@
+{
+    "advancededitormacro_id_pp": {
+      "name": "advancededitormacro_id",
+      "in": "path",
+      "description": "Advanced Editor Macro internal identifier",
+      "required": true,
+      "type": "integer"
+    }
+}
index 1e4ec4a..8e0af9b 100644 (file)
   "/checkouts/{checkout_id}/allows_renewal": {
     "$ref": "paths/checkouts.json#/~1checkouts~1{checkout_id}~1allows_renewal"
   },
+  "/advancededitormacros": {
+    "$ref": "paths/advancededitormacros.json#/~1advancededitormacros"
+  },
+  "/advancededitormacros/{advancededitormacro_id}": {
+    "$ref": "paths/advancededitormacros.json#/~1advancededitormacros~1{advancededitormacro_id}"
+  },
+  "/advancededitormacros/shared": {
+    "$ref": "paths/advancededitormacros.json#/~1advancededitormacros~1shared"
+  },
+  "/advancededitormacros/shared/{advancededitormacro_id}": {
+    "$ref": "paths/advancededitormacros.json#/~1advancededitormacros~1shared~1{advancededitormacro_id}"
+  },
   "/patrons": {
     "$ref": "paths/patrons.json#/~1patrons"
   },
diff --git a/api/v1/swagger/paths/advancededitormacros.json b/api/v1/swagger/paths/advancededitormacros.json
new file mode 100644 (file)
index 0000000..7c7eb93
--- /dev/null
@@ -0,0 +1,521 @@
+{
+  "/advancededitormacros": {
+    "get": {
+      "x-mojo-to": "AdvancedEditorMacro#list",
+      "operationId": "listMacro",
+      "tags": ["advancededitormacro"],
+      "produces": [
+        "application/json"
+      ],
+      "parameters": [
+        {
+          "name": "name",
+          "in": "query",
+          "description": "Case insensative search on macro name",
+          "required": false,
+          "type": "string"
+        },
+        {
+          "name": "macro_text",
+          "in": "query",
+          "description": "Case insensative search on macro text",
+          "required": false,
+          "type": "string"
+        },
+        {
+          "name": "patron_id",
+          "in": "query",
+          "description": "Search on internal patron_id",
+          "required": false,
+          "type": "string"
+        },
+        {
+          "name": "shared",
+          "in": "query",
+          "description": "Search on shared macros",
+          "required": false,
+          "type": "string"
+        }
+      ],
+      "responses": {
+        "200": {
+          "description": "A list of macros",
+          "schema": {
+            "type": "array",
+            "items": {
+              "$ref": "../definitions.json#/advancededitormacro"
+            }
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+            "editcatalogue": "advanced_editor"
+        }
+      }
+    },
+    "post": {
+      "x-mojo-to": "AdvancedEditorMacro#add",
+      "operationId": "addAdvancedEditorMacro",
+      "tags": ["advancededitormacro"],
+      "parameters": [{
+        "name": "body",
+        "in": "body",
+        "description": "A JSON object containing informations about the new macro",
+        "required": true,
+        "schema": {
+          "$ref": "../definitions.json#/advancededitormacro"
+        }
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "201": {
+          "description": "Macro added",
+          "schema": {
+            "$ref": "../definitions.json#/advancededitormacro"
+          }
+        },
+        "401": {
+          "description": "Authentication required",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+          "editcatalogue": "advanced_editor"
+        }
+      }
+    }
+  },
+  "/advancededitormacros/shared": {
+    "post": {
+      "x-mojo-to": "AdvancedEditorMacro#add_shared",
+      "operationId": "addsharedAdvancedEditorMacro",
+      "tags": ["advancededitormacro"],
+      "parameters": [{
+        "name": "body",
+        "in": "body",
+        "description": "A JSON object containing informations about the new macro",
+        "required": true,
+        "schema": {
+          "$ref": "../definitions.json#/advancededitormacro"
+        }
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "201": {
+          "description": "Macro added",
+          "schema": {
+            "$ref": "../definitions.json#/advancededitormacro"
+          }
+        },
+        "401": {
+          "description": "Authentication required",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+          "editcatalogue": "advanced_editor",
+          "editcatalogue": "create_shared_macros"
+        }
+      }
+    }
+  },
+  "/advancededitormacros/{advancededitormacro_id}": {
+    "get": {
+      "x-mojo-to": "AdvancedEditorMacro#get",
+      "operationId": "getAdvancedEditorMacro",
+      "tags": ["advancededitormacros"],
+      "parameters": [{
+        "$ref": "../parameters.json#/advancededitormacro_id_pp"
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "A macro",
+          "schema": {
+            "$ref": "../definitions.json#/advancededitormacro"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "AdvancedEditorMacro not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+            "editcatalogue": "advanced_editor"
+        }
+      }
+    },
+    "put": {
+      "x-mojo-to": "AdvancedEditorMacro#update",
+      "operationId": "updateAdvancedEditorMacro",
+      "tags": ["advancededitormacros"],
+      "parameters": [{
+        "$ref": "../parameters.json#/advancededitormacro_id_pp"
+      }, {
+        "name": "body",
+        "in": "body",
+        "description": "An advanced editor macro object",
+        "required": true,
+        "schema": {
+          "$ref": "../definitions.json#/advancededitormacro"
+        }
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "An advanced editor macro",
+          "schema": {
+            "$ref": "../definitions.json#/advancededitormacro"
+          }
+        },
+        "401": {
+          "description": "Authentication required",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "Macro not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+          "editcatalogue": "advanced_editor"
+        }
+      }
+    },
+    "delete": {
+      "x-mojo-to": "AdvancedEditorMacro#delete",
+      "operationId": "deleteAdvancedEditorMacro",
+      "tags": ["advancededitormacros"],
+      "parameters": [{
+        "$ref": "../parameters.json#/advancededitormacro_id_pp"
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "Advanced editor macro deleted",
+          "schema": {
+            "type": "string"
+          }
+        },
+        "401": {
+          "description": "Authentication required",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "Macro not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+          "editcatalogue": "advanced_editor"
+        }
+      }
+    }
+  },
+  "/advancededitormacros/shared/{advancededitormacro_id}": {
+    "get": {
+      "x-mojo-to": "AdvancedEditorMacro#get_shared",
+      "operationId": "getsharedAdvancedEditorMacro",
+      "tags": ["advancededitormacros"],
+      "parameters": [{
+        "$ref": "../parameters.json#/advancededitormacro_id_pp"
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "A macro",
+          "schema": {
+            "$ref": "../definitions.json#/advancededitormacro"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "AdvancedEditorMacro not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+            "editcatalogue": "advanced_editor"
+        }
+      }
+    },
+    "put": {
+      "x-mojo-to": "AdvancedEditorMacro#update_shared",
+      "operationId": "updatesharedAdvancedEditorMacro",
+      "tags": ["advancededitormacros"],
+      "parameters": [{
+        "$ref": "../parameters.json#/advancededitormacro_id_pp"
+      }, {
+        "name": "body",
+        "in": "body",
+        "description": "An advanced editor macro object",
+        "required": true,
+        "schema": {
+          "$ref": "../definitions.json#/advancededitormacro"
+        }
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "An advanced editor macro",
+          "schema": {
+            "$ref": "../definitions.json#/advancededitormacro"
+          }
+        },
+        "401": {
+          "description": "Authentication required",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "Macro not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+          "editcatalogue": "advanced_editor",
+          "editcatalogue": "create_shared_macros"
+        }
+      }
+    },
+    "delete": {
+      "x-mojo-to": "AdvancedEditorMacro#delete_shared",
+      "operationId": "deletesharedAdvancedEditorMacro",
+      "tags": ["advancededitormacros"],
+      "parameters": [{
+        "$ref": "../parameters.json#/advancededitormacro_id_pp"
+      }],
+      "produces": [
+        "application/json"
+      ],
+      "responses": {
+        "200": {
+          "description": "Advanced editor macro deleted",
+          "schema": {
+            "type": "string"
+          }
+        },
+        "401": {
+          "description": "Authentication required",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "403": {
+          "description": "Access forbidden",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "404": {
+          "description": "Macro not found",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "500": {
+          "description": "Internal error",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        },
+        "503": {
+          "description": "Under maintenance",
+          "schema": {
+            "$ref": "../definitions.json#/error"
+          }
+        }
+      },
+      "x-koha-authorization": {
+        "permissions": {
+          "editcatalogue": "advanced_editor",
+          "editcatalogue": "delete_shared_macros"
+        }
+      }
+    }
+  }
+}
index 19bc8dc..0594264 100644 (file)
@@ -3,6 +3,11 @@
     "type": "integer",
     "description": "Internal biblio identifier"
   },
+  "advancededitormacro_id": {
+    "type": "integer",
+    "description": "Internal advanced editor macro identifier",
+    "readOnly": true
+  },
   "patron_id": {
     "type": "integer",
     "description": "Internal patron identifier"
diff --git a/t/db_dependent/api/v1/advanced_editor_macros.t b/t/db_dependent/api/v1/advanced_editor_macros.t
new file mode 100644 (file)
index 0000000..025c73f
--- /dev/null
@@ -0,0 +1,475 @@
+#!/usr/bin/env perl
+
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Test::More tests => 5;
+use Test::Mojo;
+use Test::Warn;
+
+use t::lib::TestBuilder;
+use t::lib::Mocks;
+
+use Koha::AdvancedEditorMacros;
+use Koha::Database;
+
+my $schema  = Koha::Database->new->schema;
+my $builder = t::lib::TestBuilder->new;
+
+t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
+
+my $t = Test::Mojo->new('Koha::REST::V1');
+
+$schema->storage->txn_begin;
+
+subtest 'list() tests' => sub {
+    plan tests => 8;
+
+
+    my $patron_1 = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { flags => 9 }
+    });
+    my $patron_2 = $builder->build_object({
+        class => 'Koha::Patrons',
+    });
+    my $password = 'thePassword123';
+    $patron_1->set_password({ password => $password, skip_validation => 1 });
+    my $userid = $patron_1->userid;
+
+    # Create test context
+    my $macro_1 = $builder->build_object({ class => 'Koha::AdvancedEditorMacros', value =>
+        {
+            name => 'Test1',
+            macro => 'delete 100',
+            borrowernumber => $patron_1->borrowernumber,
+        }
+    });
+    my $macro_2 = $builder->build_object({ class => 'Koha::AdvancedEditorMacros', value =>
+        {
+            name => 'Test2',
+            macro => 'delete 100',
+            borrowernumber => $patron_1->borrowernumber,
+            shared=> 1,
+        }
+    });
+    my $macro_3 = $builder->build_object({ class => 'Koha::AdvancedEditorMacros', value =>
+        {
+            name => 'Test3',
+            macro => 'delete 100',
+            borrowernumber => $patron_2->borrowernumber,
+        }
+    });
+    my $macro_4 = $builder->build_object({ class => 'Koha::AdvancedEditorMacros', value =>
+        {
+            name => 'Test4',
+            macro => 'delete 100',
+            borrowernumber => $patron_2->borrowernumber,
+            shared => 1,
+        }
+    });
+
+    my $macros_index = Koha::AdvancedEditorMacros->search({ -or => { shared => 1, borrowernumber => $patron_1->borrowernumber } })->count-1;
+    ## Authorized user tests
+    # Make sure we are returned with the correct amount of macros
+    $t->get_ok( "//$userid:$password@/api/v1/advancededitormacros" )
+      ->status_is( 200, 'SWAGGER3.2.2' )
+      ->json_has('/' . $macros_index . '/macro_id')
+      ->json_hasnt('/' . ($macros_index + 1) . '/macro_id');
+
+    subtest 'query parameters' => sub {
+
+        plan tests => 15;
+        $t->get_ok("//$userid:$password@/api/v1/advancededitormacros?name=" . $macro_2->name)
+          ->status_is(200)
+          ->json_has( [ $macro_2 ] );
+        $t->get_ok("//$userid:$password@/api/v1/advancededitormacros?name=" . $macro_3->name)
+          ->status_is(200)
+          ->json_has( [ ] );
+        $t->get_ok("//$userid:$password@/api/v1/advancededitormacros?macro_text=delete 100")
+          ->status_is(200)
+          ->json_has( [ $macro_1, $macro_2, $macro_4 ] );
+        $t->get_ok("//$userid:$password@/api/v1/advancededitormacros?patron_id=" . $patron_1->borrowernumber)
+          ->status_is(200)
+          ->json_has( [ $macro_1, $macro_2 ] );
+        $t->get_ok("//$userid:$password@/api/v1/advancededitormacros?shared=1")
+          ->status_is(200)
+          ->json_has( [ $macro_2, $macro_4 ] );
+    };
+
+    # Warn on unsupported query parameter
+    $t->get_ok( "//$userid:$password@/api/v1/advancededitormacros?macro_blah=blah" )
+      ->status_is(400)
+      ->json_is( [{ path => '/query/macro_blah', message => 'Malformed query string'}] );
+
+};
+
+subtest 'get() tests' => sub {
+
+    plan tests => 15;
+
+    my $patron = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { flags => 1 }
+    });
+    my $password = 'thePassword123';
+    $patron->set_password({ password => $password, skip_validation => 1 });
+    my $userid = $patron->userid;
+
+    my $macro_1 = $builder->build_object( { class => 'Koha::AdvancedEditorMacros', value => {
+            shared => 1,
+        }
+    });
+    my $macro_2 = $builder->build_object( { class => 'Koha::AdvancedEditorMacros', value => {
+            shared => 0,
+        }
+    });
+    my $macro_3 = $builder->build_object( { class => 'Koha::AdvancedEditorMacros', value => {
+            borrowernumber => $patron->borrowernumber,
+            shared => 0,
+        }
+    });
+
+    $t->get_ok( "//$userid:$password@/api/v1/advancededitormacros/" . $macro_1->id )
+      ->status_is( 403, 'Cannot get a shared macro via regular endpoint' )
+      ->json_is( '/error' => 'This macro is shared, you must access it via advancededitormacros/shared' );
+
+    $t->get_ok( "//$userid:$password@/api/v1/advancededitormacros/shared/" . $macro_1->id )
+      ->status_is( 200, 'Can get a shared macro via shared endpoint' )
+      ->json_is( '' => Koha::REST::V1::AdvancedEditorMacro::_to_api( $macro_1->TO_JSON ), 'Macro correctly retrieved' );
+
+    $t->get_ok( "//$userid:$password@/api/v1/advancededitormacros/" . $macro_2->id )
+      ->status_is( 403, 'Cannot access another users macro' )
+      ->json_is( '/error' => 'You do not have permission to access this macro' );
+
+    $t->get_ok( "//$userid:$password@/api/v1/advancededitormacros/" . $macro_3->id )
+      ->status_is( 200, 'Can get your own private macro' )
+      ->json_is( '' => Koha::REST::V1::AdvancedEditorMacro::_to_api( $macro_3->TO_JSON ), 'Macro correctly retrieved' );
+
+    my $non_existent_code = $macro_1->id;
+    $macro_1->delete;
+
+    $t->get_ok( "//$userid:$password@/api/v1/advancededitormacros/" . $non_existent_code )
+      ->status_is(404)
+      ->json_is( '/error' => 'Macro not found' );
+
+};
+
+subtest 'add() tests' => sub {
+
+    plan tests => 24;
+
+    my $authorized_patron = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { flags => 0 }
+    });
+    $builder->build({
+        source => 'UserPermission',
+        value  => {
+            borrowernumber => $authorized_patron->borrowernumber,
+            module_bit     => 9,
+            code           => 'advanced_editor',
+        },
+    });
+
+    my $password = 'thePassword123';
+    $authorized_patron->set_password({ password => $password, skip_validation => 1 });
+    my $auth_userid = $authorized_patron->userid;
+
+    my $unauthorized_patron = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { flags => 0 }
+    });
+    $unauthorized_patron->set_password({ password => $password, skip_validation => 1 });
+    my $unauth_userid = $unauthorized_patron->userid;
+
+    my $macro = $builder->build_object({
+        class => 'Koha::AdvancedEditorMacros',
+        value => { shared => 0 }
+    });
+    my $macro_values     = Koha::REST::V1::AdvancedEditorMacro::_to_api( $macro->TO_JSON );
+    delete $macro_values->{macro_id};
+    $macro->delete;
+
+    # Unauthorized attempt to write
+    $t->post_ok( "//$unauth_userid:$password@/api/v1/advancededitormacros" => json => $macro_values )
+      ->status_is(403);
+
+    # Authorized attempt to write invalid data
+    my $macro_with_invalid_field = { %$macro_values };
+    $macro_with_invalid_field->{'big_mac_ro'} = 'Mac attack';
+
+    $t->post_ok( "//$auth_userid:$password@/api/v1/advancededitormacros" => json => $macro_with_invalid_field )
+      ->status_is(400)
+      ->json_is(
+        "/errors" => [
+            {
+                message => "Properties not allowed: big_mac_ro.",
+                path    => "/body"
+            }
+        ]
+    );
+
+    # Authorized attempt to write
+    $t->post_ok( "//$auth_userid:$password@/api/v1/advancededitormacros" => json => $macro_values )
+      ->status_is( 201, 'SWAGGER3.2.1' )
+      ->json_has( '/macro_id', 'We generated a new id' )
+      ->json_is( '/name' => $macro_values->{name}, 'The name matches what we supplied' )
+      ->json_is( '/macro_text' => $macro_values->{macro_text}, 'The text matches what we supplied' )
+      ->json_is( '/patron_id' => $macro_values->{patron_id}, 'The borrower matches the borrower who submitted' )
+      ->json_is( '/shared' => 0, 'The macro is not shared' )
+      ->header_like( Location => qr|^\/api\/v1\/advancededitormacros\/d*|, 'Correct location' );
+
+    # save the library_id
+    my $macro_id = 999;
+
+    # Authorized attempt to create with existing id
+    $macro_values->{macro_id} = $macro_id;
+
+    $t->post_ok( "//$auth_userid:$password@/api/v1/advancededitormacros" => json => $macro_values )
+      ->status_is(400)
+      ->json_is( '/errors' => [
+            {
+                message => "Read-only.",
+                path   => "/body/macro_id"
+            }
+        ]
+    );
+
+    $macro_values->{shared} = 1;
+    delete $macro_values->{macro_id};
+
+    # Unauthorized attempt to write a shared macro on private endpoint
+    $t->post_ok( "//$auth_userid:$password@/api/v1/advancededitormacros" => json => $macro_values )
+      ->status_is(403);
+    # Unauthorized attempt to write a private macro on shared endpoint
+    $t->post_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/shared" => json => $macro_values )
+      ->status_is(403);
+
+    $builder->build({
+        source => 'UserPermission',
+        value  => {
+            borrowernumber => $authorized_patron->borrowernumber,
+            module_bit     => 9,
+            code           => 'create_shared_macros',
+        },
+    });
+
+    # Authorized attempt to write a shared macro on private endpoint
+    $t->post_ok( "//$auth_userid:$password@/api/v1/advancededitormacros" => json => $macro_values )
+      ->status_is(403);
+
+    # Authorized attempt to write a shared macro on shared endpoint
+    $t->post_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/shared" => json => $macro_values )
+      ->status_is(201);
+
+};
+
+subtest 'update() tests' => sub {
+    plan tests => 32;
+
+    my $authorized_patron = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { flags => 0 }
+    });
+    $builder->build({
+        source => 'UserPermission',
+        value  => {
+            borrowernumber => $authorized_patron->borrowernumber,
+            module_bit     => 9,
+            code           => 'advanced_editor',
+        },
+    });
+
+    my $password = 'thePassword123';
+    $authorized_patron->set_password({ password => $password, skip_validation => 1 });
+    my $auth_userid = $authorized_patron->userid;
+
+    my $unauthorized_patron = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { flags => 0 }
+    });
+    $unauthorized_patron->set_password({ password => $password, skip_validation => 1 });
+    my $unauth_userid = $unauthorized_patron->userid;
+
+    my $macro = $builder->build_object({
+        class => 'Koha::AdvancedEditorMacros',
+        value => { borrowernumber => $authorized_patron->borrowernumber, shared => 0 }
+    });
+    my $macro_2 = $builder->build_object({
+        class => 'Koha::AdvancedEditorMacros',
+        value => { borrowernumber => $unauthorized_patron->borrowernumber, shared => 0 }
+    });
+    my $macro_id = $macro->id;
+    my $macro_2_id = $macro_2->id;
+    my $macro_values     = Koha::REST::V1::AdvancedEditorMacro::_to_api( $macro->TO_JSON );
+    delete $macro_values->{macro_id};
+
+    # Unauthorized attempt to update
+    $t->put_ok( "//$unauth_userid:$password@/api/v1/advancededitormacros/$macro_id"
+                    => json => { name => 'New unauthorized name change' } )
+      ->status_is(403);
+
+    # Attempt partial update on a PUT
+    my $macro_with_missing_field = {
+        name => "Call it macro-roni",
+    };
+
+    $t->put_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/$macro_id" => json => $macro_with_missing_field )
+      ->status_is(400)
+      ->json_has( "/errors" =>
+          [ { message => "Missing property.", path => "/body/macro_text" } ]
+      );
+
+    my $macro_update = {
+        name => "Macro-update",
+        macro_text => "delete 100",
+        patron_id => $authorized_patron->borrowernumber,
+        shared => 0,
+    };
+
+    my $test = $t->put_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/$macro_id" => json => $macro_update )
+      ->status_is(200, 'Authorized user can update a macro')
+      ->json_is( '/macro_id' => $macro_id, 'We get the id back' )
+      ->json_is( '/name' => $macro_update->{name}, 'We get the name back' )
+      ->json_is( '/macro_text' => $macro_update->{macro_text}, 'We get the text back' )
+      ->json_is( '/patron_id' => $macro_update->{patron_id}, 'We get the patron_id back' )
+      ->json_is( '/shared' => $macro_update->{shared}, 'It should still not be shared' );
+
+    # Now try to make the macro shared
+    $macro_update->{shared} = 1;
+
+    $t->put_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/shared/$macro_id" => json => $macro_update )
+      ->status_is(403, 'Cannot make your macro shared on private endpoint');
+    $t->put_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/shared/$macro_id" => json => $macro_update )
+      ->status_is(403, 'Cannot make your macro shared without permission');
+
+    $builder->build({
+        source => 'UserPermission',
+        value  => {
+            borrowernumber => $authorized_patron->borrowernumber,
+            module_bit     => 9,
+            code           => 'create_shared_macros',
+        },
+    });
+
+    $t->put_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/$macro_id" => json => $macro_update )
+      ->status_is(403, 'Cannot make your macro shared on the private endpoint');
+
+    $t->put_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/shared/$macro_id" => json => $macro_update )
+      ->status_is(200, 'Can update macro to shared with permission')
+      ->json_is( '/macro_id' => $macro_id, 'We get back the id' )
+      ->json_is( '/name' => $macro_update->{name}, 'We get back the name' )
+      ->json_is( '/macro_text' => $macro_update->{macro_text}, 'We get back the text' )
+      ->json_is( '/patron_id' => $macro_update->{patron_id}, 'We get back our patron id' )
+      ->json_is( '/shared' => 1, 'It is shared' );
+
+    # Authorized attempt to write invalid data
+    my $macro_with_invalid_field = { %$macro_update };
+    $macro_with_invalid_field->{'big_mac_ro'} = 'Mac attack';
+
+    $t->put_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/$macro_id" => json => $macro_with_invalid_field )
+      ->status_is(400)
+      ->json_is(
+        "/errors" => [
+            {
+                message => "Properties not allowed: big_mac_ro.",
+                path    => "/body"
+            }
+        ]
+    );
+
+    my $non_existent_macro = $builder->build_object({class => 'Koha::AdvancedEditorMacros'});
+    my $non_existent_code = $non_existent_macro->id;
+    $non_existent_macro->delete;
+
+    $t->put_ok("//$auth_userid:$password@/api/v1/advancededitormacros/$non_existent_code" => json => $macro_update)
+      ->status_is(404);
+
+    $t->put_ok("//$auth_userid:$password@/api/v1/advancededitormacros/$macro_2_id" => json => $macro_update)
+      ->status_is(403, "Cannot update other borrowers private macro");
+};
+
+subtest 'delete() tests' => sub {
+    plan tests => 12;
+
+    my $authorized_patron = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { flags => 0 }
+    });
+    $builder->build({
+        source => 'UserPermission',
+        value  => {
+            borrowernumber => $authorized_patron->borrowernumber,
+            module_bit     => 9,
+            code           => 'advanced_editor',
+        },
+    });
+
+    my $password = 'thePassword123';
+    $authorized_patron->set_password({ password => $password, skip_validation => 1 });
+    my $auth_userid = $authorized_patron->userid;
+
+    my $unauthorized_patron = $builder->build_object({
+        class => 'Koha::Patrons',
+        value => { flags => 0 }
+    });
+    $unauthorized_patron->set_password({ password => $password, skip_validation => 1 });
+    my $unauth_userid = $unauthorized_patron->userid;
+
+    my $macro = $builder->build_object({
+        class => 'Koha::AdvancedEditorMacros',
+        value => { borrowernumber => $authorized_patron->borrowernumber, shared => 0 }
+    });
+    my $macro_2 = $builder->build_object({
+        class => 'Koha::AdvancedEditorMacros',
+        value => { borrowernumber => $unauthorized_patron->borrowernumber, shared => 0 }
+    });
+    my $macro_id = $macro->id;
+    my $macro_2_id = $macro_2->id;
+
+    # Unauthorized attempt to delete
+    $t->delete_ok( "//$unauth_userid:$password@/api/v1/advancededitormacros/$macro_2_id")
+      ->status_is(403, "Cannot delete macro without permission");
+
+    $t->delete_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/$macro_id")
+      ->status_is(200, 'Can delete macro with permission');
+
+    $t->delete_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/$macro_2_id")
+      ->status_is(403, 'Cannot delete other users macro with permission');
+
+    $macro_2->shared(1)->store();
+
+    $t->delete_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/shared/$macro_2_id")
+      ->status_is(403, 'Cannot delete other users shared macro without permission');
+
+    $builder->build({
+        source => 'UserPermission',
+        value  => {
+            borrowernumber => $authorized_patron->borrowernumber,
+            module_bit     => 9,
+            code           => 'delete_shared_macros',
+        },
+    });
+    $t->delete_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/$macro_2_id")
+      ->status_is(403, 'Cannot delete other users shared macro with permission on private endpoint');
+    $t->delete_ok( "//$auth_userid:$password@/api/v1/advancededitormacros/shared/$macro_2_id")
+      ->status_is(200, 'Can delete other users shared macro with permission');
+
+};
+
+$schema->storage->txn_rollback;