diff --git a/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java b/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java
index 6ca9f25a2392c39a5a1261f27a40098c987d8105..b714c9481b45cbcb4adb34d671669baaab877745 100644
--- a/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java
+++ b/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java
@@ -6,14 +6,7 @@
 package it.inaf.oats.vospace;
 
 import it.inaf.ia2.aa.data.User;
-import it.inaf.oats.vospace.datamodel.NodeUtils;
-import it.inaf.oats.vospace.exception.LinkFoundException;
-import it.inaf.oats.vospace.exception.NodeNotFoundException;
-import it.inaf.oats.vospace.exception.PermissionDeniedException;
-import it.inaf.oats.vospace.persistence.NodeDAO;
-import java.util.List;
 import javax.servlet.http.HttpServletRequest;
-import net.ivoa.xml.vospace.v2.Node;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -23,70 +16,25 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-
 @RestController
-public class DeleteNodeController extends BaseNodeController  {
-    
-    private static final Logger LOG = LoggerFactory.getLogger(DeleteNodeController.class);
-    
+public class DeleteNodeController extends BaseNodeController {
+
     @Autowired
-    private NodeDAO nodeDAO;
+    DeleteNodeService deleteNodeService;
+
+    private static final Logger LOG = LoggerFactory.getLogger(DeleteNodeController.class);
 
     @DeleteMapping(value = {"/nodes", "/nodes/**"},
             produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
     public ResponseEntity<String> deleteNode(HttpServletRequest request, User principal) {
-        
+
         String path = getPath();
         LOG.debug("deleteNode called for path {}", path);
-        
-        // Check if the node is present, 
-        // if the node does not exist the service SHALL throw a HTTP 404 status code 
-        // including a NodeNotFound fault in the entity-body 
-        // If note present, got it
-        Node toBeDeletedNode = nodeDAO.listNode(path)
-                        .orElseThrow(() -> new NodeNotFoundException(path));               
-        
-        // If a parent node in the URI path is a LinkNode, the service SHALL throw 
-        // a HTTP 400 status code including a LinkFound fault in the entity-body.
-        // For example, given the URI path /a/b/c, the service must throw a HTTP 400 
-        // status code including a LinkFound fault in the entity-body if either /a 
-        // or /a/b are LinkNodes.
-        List<String> pathComponents = NodeUtils.subPathComponents(path);
-        if (pathComponents.isEmpty()) { 
-            
-            // Manage root node
-            throw PermissionDeniedException.forPath("/");
-            
-        } else {
-            
-            // Manage all precursors in full path
-            for (int i = 0; i < pathComponents.size(); i++) { 
-                String tmpPath = pathComponents.get(i);
-                Node mynode = nodeDAO.listNode(tmpPath)
-                        .orElseThrow(() -> new NodeNotFoundException(tmpPath));
-                if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size()-1) // a LinkNode leaf can be deleted
-                    throw new LinkFoundException(tmpPath);
-                            
-            }
-                    
-        }
-        
-        if (!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) {
-            throw PermissionDeniedException.forPath(path);
-        }
-                
-        try {
-            nodeDAO.deleteNode(path);
-            return ResponseEntity.ok("Node deleted");
-        } catch(Exception ex) {
-            return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
-        }
-        
-    }
-    
-}
 
+        deleteNodeService.doPreliminaryChecks(path);
+        deleteNodeService.deleteNode(path, principal);
+        return ResponseEntity.ok("Node deleted");
 
-    
-    
+    }
 
+}
diff --git a/src/main/java/it/inaf/oats/vospace/DeleteNodeService.java b/src/main/java/it/inaf/oats/vospace/DeleteNodeService.java
new file mode 100644
index 0000000000000000000000000000000000000000..4481a30574567809c95e495591d8222ede7ecb46
--- /dev/null
+++ b/src/main/java/it/inaf/oats/vospace/DeleteNodeService.java
@@ -0,0 +1,99 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package it.inaf.oats.vospace;
+
+import it.inaf.oats.vospace.datamodel.NodeUtils;
+import it.inaf.oats.vospace.exception.LinkFoundException;
+import it.inaf.oats.vospace.exception.NodeNotFoundException;
+import it.inaf.oats.vospace.exception.PermissionDeniedException;
+import it.inaf.oats.vospace.persistence.NodeDAO;
+import java.util.List;
+import net.ivoa.xml.vospace.v2.Node;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.transaction.annotation.Isolation;
+import it.inaf.ia2.aa.data.User;
+import it.inaf.oats.vospace.exception.InternalFaultException;
+import it.inaf.oats.vospace.exception.NodeBusyException;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ *
+ * @author Nicola Fulvio Calabria <nicola.calabria at inaf.it>
+ */
+@Service
+@EnableTransactionManagement
+public class DeleteNodeService {
+
+    @Autowired
+    protected NodeDAO nodeDao;
+
+    @Value("${vospace-authority}")
+    protected String authority;
+
+    @Transactional(rollbackFor = {Exception.class},
+            isolation = Isolation.REPEATABLE_READ)
+    public void deleteNode(String path, User principal) {
+
+        Node toBeDeletedNode = nodeDao.listNode(path)
+                .orElseThrow(() -> new NodeNotFoundException(path));
+
+        if (!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) {
+            throw PermissionDeniedException.forPath(path);
+        }
+
+        Long nodeId = nodeDao.getNodeId(path).get();
+
+        if (nodeDao.isBranchBusy(nodeId)) {
+            throw new NodeBusyException(path);
+        }
+
+        if (nodeDao.isBranchImmutable(nodeId)) {
+            throw new InternalFaultException("Target branch contains immutable nodes");
+        }
+
+        nodeDao.deleteNode(path);
+
+    }
+
+    public void doPreliminaryChecks(String path) {
+        // Check if the node is present, 
+        // if the node does not exist the service SHALL throw a HTTP 404 status code 
+        // including a NodeNotFound fault in the entity-body 
+        // If note present, got it
+        nodeDao.listNode(path)
+                .orElseThrow(() -> new NodeNotFoundException(path));
+
+        // If a parent node in the URI path is a LinkNode, the service SHALL throw 
+        // a HTTP 400 status code including a LinkFound fault in the entity-body.
+        // For example, given the URI path /a/b/c, the service must throw a HTTP 400 
+        // status code including a LinkFound fault in the entity-body if either /a 
+        // or /a/b are LinkNodes.        
+        if (path.equals("/")) {
+            
+            // Manage root node
+            throw PermissionDeniedException.forPath("/");
+
+        } else {
+            List<String> pathComponents = NodeUtils.subPathComponents(path);
+
+            // Manage all precursors in full path
+            for (int i = 0; i < pathComponents.size(); i++) {
+                String tmpPath = pathComponents.get(i);
+                Node mynode = nodeDao.listNode(tmpPath)
+                        .orElseThrow(() -> new NodeNotFoundException(tmpPath));
+                if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size() - 1) // a LinkNode leaf can be deleted
+                {
+                    throw new LinkFoundException(tmpPath);
+                }
+
+            }
+
+        }
+    }
+}
diff --git a/src/main/java/it/inaf/oats/vospace/ImmutableService.java b/src/main/java/it/inaf/oats/vospace/ImmutableService.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec7eb0ada3d30c85d0691964c2d447d11d38e852
--- /dev/null
+++ b/src/main/java/it/inaf/oats/vospace/ImmutableService.java
@@ -0,0 +1,40 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package it.inaf.oats.vospace;
+
+import it.inaf.ia2.aa.data.User;
+import it.inaf.oats.vospace.persistence.NodeDAO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ *
+ * @author Nicola Fulvio Calabria <nicola.calabria at inaf.it>
+ */
+@Service
+@EnableTransactionManagement
+public class ImmutableService {
+
+    @Autowired
+    protected NodeDAO nodeDao;
+
+    @Value("${vospace-authority}")
+    protected String authority;
+
+    @Transactional(rollbackFor = {Exception.class}, isolation = Isolation.REPEATABLE_READ)
+    public void setBranchImmutable(String rootNodeURI, boolean setImmutable, User user) {
+        
+        String rootNodeVosPath = URIUtils.returnVosPathFromNodeURI(rootNodeURI, authority);
+        
+        // Check if branch is busy
+        
+    }
+
+}
diff --git a/src/main/java/it/inaf/oats/vospace/MoveService.java b/src/main/java/it/inaf/oats/vospace/MoveService.java
index 1d22cf604ce0ca19f4cf8ee233d8b7362c9c01f8..a09c7df26c77b8d4f10982e7464a554bdf16f3ec 100644
--- a/src/main/java/it/inaf/oats/vospace/MoveService.java
+++ b/src/main/java/it/inaf/oats/vospace/MoveService.java
@@ -68,6 +68,11 @@ public class MoveService extends AbstractNodeService {
                 throw new NodeBusyException(sourcePath);
             }
 
+            // TODO create immutable node exception flavor
+            if (nodeDao.isBranchImmutable(sourceId)) {
+                throw new InternalFaultException("Source branch contains immutable nodes");
+            }
+
             if (!nodeDao.isBranchWritable(sourceId, user.getName(), user.getGroups())) {
                 throw PermissionDeniedException.forPath(sourcePath);
             }           
@@ -84,6 +89,7 @@ public class MoveService extends AbstractNodeService {
                 if(snd.isPermissionDenied()) throw PermissionDeniedException.forPath(destinationPath);                                                
                 if(!snd.isWritable()) throw new InternalFaultException("Destination is not writable: "+ destinationPath);
                 if(!snd.isContainer()) throw new InternalFaultException("Existing destination is not a container: " + destinationPath);
+                if(snd.isImmutable()) throw new InternalFaultException("Destination is immutable: " + destinationPath);
                 
                 destinationNodeLtreePath = snd.getDestinationNodeLtreePath();
                 
diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
index f07c4ce5be9a0ef976531dc6daf128d84db754d9..a27b95ada5564b9cd84642459e7a221505550822 100644
--- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
+++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java
@@ -256,7 +256,8 @@ public class NodeDAO {
                 + "((SELECT COUNT(*) FROM (SELECT UNNEST(?) INTERSECT SELECT UNNEST(n.group_write)) AS allowed_groups ) = 0 AND\n"
                 + "n.creator_id <> ?) AS is_permission_denied,\n"
                 + "n.type = 'container' AS is_container,\n"
-                + "n.job_id IS NOT NULL AS busy_state\n"
+                + "n.job_id IS NOT NULL AS busy_state,\n"
+                + "n.immutable AS is_immutable\n"
                 + "FROM node n \n"
                 + "LEFT JOIN location loc ON loc.location_id = n.location_id\n"
                 + "WHERE n.node_id = id_from_vos_path(?)\n";
@@ -286,8 +287,9 @@ public class NodeDAO {
             Boolean isBusy = rs.getBoolean("busy_state");
             Boolean isPermissionDenied = rs.getBoolean("is_permission_denied");
             Boolean isSticky = rs.getBoolean("is_sticky");
+            Boolean isImmutable = rs.getBoolean("is_immutable");
 
-            ShortNodeDescriptor result = new ShortNodeDescriptor(nodePath, isContainer, isWritable, isBusy, isPermissionDenied, isSticky);
+            ShortNodeDescriptor result = new ShortNodeDescriptor(nodePath, isContainer, isWritable, isBusy, isPermissionDenied, isSticky, isImmutable);
 
             return Optional.of(result);
         });
@@ -403,6 +405,30 @@ public class NodeDAO {
             return ps;
         });
     }
+    
+    public void setBranchImmutable(Long rootNodeId, boolean setImmutable) {
+        String sql = "UPDATE node c SET immutable = ? "
+                + "FROM node r "
+                + "WHERE r.node_id = ? "
+                + "AND r.path @> c.path";
+
+        jdbcTemplate.update(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setBoolean(1, setImmutable);
+            ps.setLong(2, rootNodeId);
+            return ps;
+        });        
+    }
+
+    public boolean isBranchImmutable(long parentNodeId) {
+
+        String sql = "SELECT COUNT(c.node_id) > 0 "
+                + "FROM node n "
+                + "JOIN node c ON c.path <@ n.path "
+                + "WHERE n.node_id = ? AND c.immutable IS TRUE";
+
+        return jdbcTemplate.queryForObject(sql, new Object[]{parentNodeId}, new int[]{Types.BIGINT}, Boolean.class);
+    }
 
     public void releaseBusyNodesByJobId(String jobId) {
         String sql = "UPDATE node SET job_id = NULL WHERE job_id = ?";
@@ -742,14 +768,16 @@ public class NodeDAO {
         private final boolean busy;
         private final boolean permissionDenied;
         private final boolean sticky;
+        private final boolean immutable;
 
-        public ShortNodeDescriptor(String nodeLtreePath, boolean container, boolean writable, boolean busy, boolean permissionDenied, boolean sticky) {
+        public ShortNodeDescriptor(String nodeLtreePath, boolean container, boolean writable, boolean busy, boolean permissionDenied, boolean sticky, boolean immutable) {
             this.nodeLtreePath = nodeLtreePath;
             this.container = container;
             this.writable = writable;
             this.busy = busy;
             this.permissionDenied = permissionDenied;
             this.sticky = sticky;
+            this.immutable = immutable;
         }
 
         public String getDestinationNodeLtreePath() {
@@ -760,6 +788,10 @@ public class NodeDAO {
             return container;
         }
 
+        public boolean isImmutable() {
+            return immutable;
+        }
+
         public boolean isWritable() {
             return writable;
         }
diff --git a/src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java b/src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java
deleted file mode 100644
index 0d91c90e3eadf4fb45dda39e41ad3a74aee3748c..0000000000000000000000000000000000000000
--- a/src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * This file is part of vospace-rest
- * Copyright (C) 2021 Istituto Nazionale di Astrofisica
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package it.inaf.oats.vospace;
-
-import it.inaf.oats.vospace.datamodel.NodeProperties;
-import it.inaf.oats.vospace.persistence.NodeDAO;
-import java.util.List;
-import java.util.Optional;
-import net.ivoa.xml.vospace.v2.ContainerNode;
-import net.ivoa.xml.vospace.v2.DataNode;
-import net.ivoa.xml.vospace.v2.LinkNode;
-import net.ivoa.xml.vospace.v2.Node;
-import net.ivoa.xml.vospace.v2.Property;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.test.web.servlet.MockMvc;
-import org.junit.jupiter.api.Test;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-@SpringBootTest
-@ContextConfiguration(classes = {TokenFilterConfig.class})
-@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true")
-@AutoConfigureMockMvc 
-public class DeleteNodeControllerTest {    
-    
-    private static final String URI_PREFIX = "vos://example.com!vospace";
-    
-    @MockBean
-    private NodeDAO nodeDao;
-
-    @Autowired
-    private MockMvc mockMvc;
-
-    
-    @Test
-    public void testDeleteRootNode() throws Exception {
-        
-        when(nodeDao.listNode(eq("/"))).thenReturn(getRootNode());
-
-        mockMvc.perform(MockMvcRequestBuilders
-                        .delete("/nodes")
-                        .header("Authorization", "Bearer user2_token"))
-                        .andExpect(status().isForbidden());
-        
-    }
-   
-    
-    @Test
-    public void testDeleteFirstLevelNode() throws Exception {
-        
-        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode()));
-        
-        mockMvc.perform(MockMvcRequestBuilders
-                        .delete("/nodes/mynode")
-                        .header("Authorization", "Bearer user2_token"))
-                        .andExpect(status().isOk());
-        
-    }
-       
-
-    
-    @Test
-    public void testDeleteMoreLevelNode() throws Exception {
-        
-        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode")));        
-        when(nodeDao.listNode(eq("/mynode/middlenode"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode")));        
-        when(nodeDao.listNode(eq("/mynode/middlenode/myfile.txt"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode/myfile.txt")));
-        
-        mockMvc.perform(MockMvcRequestBuilders
-                        .delete("/nodes/mynode/middlenode/myfile.txt")
-                        .header("Authorization", "Bearer user2_token"))
-                        .andExpect(status().isOk());
-        
-    }
-
-    
-    @Test
-    public void testLeafNodeNotFound() throws Exception {
-        
-        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode")));
-        
-        mockMvc.perform(MockMvcRequestBuilders
-                        .delete("/nodes/mynode/notexisting")
-                        .header("Authorization", "Bearer user2_token"))
-                        .andExpect(status().isNotFound());
-        
-    }
-
-    
-    @Test
-    public void testMiddleLevelNodeNotFound() throws Exception {
-        
-        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode")));     
-        when(nodeDao.listNode(eq("/mynode/middlenode/myfile.txt"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode/myfile.txt")));
-        
-        mockMvc.perform(MockMvcRequestBuilders
-                        .delete("/mynode/middlenode/myfile.txt")
-                        .header("Authorization", "Bearer user2_token"))
-                        .andExpect(status().isNotFound());
-        
-    }
-
-    
-    @Test
-    public void testLinkNodeLeafDelete() throws Exception {
-        
-        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableLinkNode("/mynode")));
-        
-        mockMvc.perform(MockMvcRequestBuilders
-                        .delete("/nodes/mynode")
-                        .header("Authorization", "Bearer user2_token"))
-                        .andExpect(status().isOk());
-        
-    }
-
-    
-    @Test
-    public void testMiddleLevelLinkNodeDelete() throws Exception {
-        
-        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode")));        
-        when(nodeDao.listNode(eq("/mynode/middlenode"))).thenReturn(Optional.of(getWritableLinkNode("/mynode/middlenode")));        
-        when(nodeDao.listNode(eq("/mynode/middlenode/myfile.txt"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode/myfile.txt")));
-        
-        mockMvc.perform(MockMvcRequestBuilders
-                        .delete("/nodes/mynode/middlenode/myfile.txt")
-                        .header("Authorization", "Bearer user2_token"))
-                        .andExpect(status().isBadRequest());
-        
-    }
-    
-
-    @Test
-    public void testDeleteMoreLevelNodeNotAllowed() throws Exception {
-        
-        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode")));        
-        when(nodeDao.listNode(eq("/mynode/middlenode"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode")));        
-        when(nodeDao.listNode(eq("/mynode/middlenode/myfile.txt"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode/myfile.txt")));
-        
-        mockMvc.perform(MockMvcRequestBuilders
-                        .delete("/nodes/mynode/middlenode/myfile.txt"))
-                        .andExpect(status().isForbidden());
-        
-    }
-    
-    
-    private Optional<Node> getRootNode() {
-        ContainerNode root = new ContainerNode();
-        root.setUri(URI_PREFIX + "/");
-        root.getNodes().add(getWritableDataNode());
-        return Optional.of(root);
-    }
-    
-    
-
-    private Node getWritableDataNode() {
-        DataNode node = new DataNode();
-        List nodeProperties = node.getProperties();
-        Property groupWriteProp = new Property();
-        groupWriteProp.setUri(NodeProperties.GROUP_WRITE_URI);
-        groupWriteProp.setValue("group1");
-        nodeProperties.add(groupWriteProp);
-        node.setUri(URI_PREFIX + "/mynode");
-        return node;
-    }
-    
-    
-    private Node getWritableDataNode(String path) {
-        DataNode node = new DataNode();
-        List nodeProperties = node.getProperties();
-        Property groupWriteProp = new Property();
-        groupWriteProp.setUri(NodeProperties.GROUP_WRITE_URI);
-        groupWriteProp.setValue("group1");
-        nodeProperties.add(groupWriteProp);
-        node.setUri(URI_PREFIX + path);
-        return node;
-    }
-    
-
-    private LinkNode getWritableLinkNode(String path) {
-        LinkNode myNode = new LinkNode();
-        // Set parent node address at /
-        myNode.setUri("vos://example.com!vospace" + path);
-        // Set groupwrite property
-        Property groups = new Property();
-        groups.setUri("ivo://ivoa.net/vospace/core#groupwrite");
-        groups.setValue("group1");
-        myNode.setProperties(List.of(groups));
-        return myNode;
-    }
-
-}
diff --git a/src/test/java/it/inaf/oats/vospace/DeleteNodeServiceTest.java b/src/test/java/it/inaf/oats/vospace/DeleteNodeServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2f4153be223c742fd2cb8e751f327e0a0b4a1d6
--- /dev/null
+++ b/src/test/java/it/inaf/oats/vospace/DeleteNodeServiceTest.java
@@ -0,0 +1,147 @@
+/*
+ * This file is part of vospace-rest
+ * Copyright (C) 2021 Istituto Nazionale di Astrofisica
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package it.inaf.oats.vospace;
+
+import it.inaf.ia2.aa.data.User;
+import it.inaf.oats.vospace.exception.InternalFaultException;
+import it.inaf.oats.vospace.exception.NodeBusyException;
+import it.inaf.oats.vospace.exception.NodeNotFoundException;
+import it.inaf.oats.vospace.exception.PermissionDeniedException;
+import it.inaf.oats.vospace.persistence.DataSourceConfigSingleton;
+import it.inaf.oats.vospace.persistence.NodeDAO;
+import java.util.Optional;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.Test;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.annotation.DirtiesContext;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+@ContextConfiguration(classes = DataSourceConfigSingleton.class)
+@TestPropertySource(locations = "classpath:test.properties", properties = {"vospace-authority=example.com!vospace", "file-service-url=http://file-service"})
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class DeleteNodeServiceTest {    
+ 
+    @Autowired
+    private DeleteNodeService deleteNodeService;
+    
+    @Autowired
+    private NodeDAO nodeDao;
+    
+    
+    @Test
+    public void testPreliminaryCheckOnRoot() {
+        assertThrows(PermissionDeniedException.class, ()->{
+            deleteNodeService.doPreliminaryChecks("/");
+        });
+    }
+    
+    @Test
+    public void testPreliminaryNodeNotFound() {
+        assertThrows(NodeNotFoundException.class, ()->{
+            deleteNodeService.doPreliminaryChecks("/nonexistent/node");
+        });
+    }
+    
+    @Test
+    public void deleteBranchTest() {
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("user3");
+
+        Optional<Long> sourceId = nodeDao.getNodeId("/test3/m1");
+        assertTrue(sourceId.isPresent());
+        Optional<Long> childId = nodeDao.getNodeId("/test3/m1/m2");
+        assertTrue(childId.isPresent());
+        // Delete
+        deleteNodeService.deleteNode("/test3/m1", user);
+
+        Optional<Long> checkSourceId = nodeDao.getNodeId("/test3/m1");
+        assertTrue(checkSourceId.isEmpty());
+        
+        Optional<Long> checkSourceIdChild = nodeDao.getNodeId("/test3/m1/m2");
+        assertTrue(checkSourceIdChild.isEmpty());        
+        
+    }
+    
+    @Test
+    public void deleteStopOnImmutableTest() {
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("user3");
+
+        Optional<Long> sourceId = nodeDao.getNodeId("/test3/m1");
+        assertTrue(sourceId.isPresent());
+        Optional<Long> childId = nodeDao.getNodeId("/test3/m1/m2");
+        assertTrue(childId.isPresent());
+        
+        nodeDao.setBranchImmutable(childId.get(), true);
+        
+        // Delete
+        assertThrows(InternalFaultException.class, 
+                ()->{ deleteNodeService.deleteNode("/test3/m1", user);}
+        );
+
+        Optional<Long> checkSourceId = nodeDao.getNodeId("/test3/m1");
+        assertTrue(checkSourceId.isPresent());
+        
+        Optional<Long> checkSourceIdChild = nodeDao.getNodeId("/test3/m1/m2");
+        assertTrue(checkSourceIdChild.isPresent());        
+    }
+    
+    @Test
+    public void deleteStopOnBusyTest() {
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("user3");
+
+        Optional<Long> sourceId = nodeDao.getNodeId("/test3/m1");
+        assertTrue(sourceId.isPresent());
+        Optional<Long> childId = nodeDao.getNodeId("/test3/m1/m2");
+        assertTrue(childId.isPresent());
+        
+        nodeDao.setBranchJobId(childId.get(), "pippo");
+        
+        // Delete
+        assertThrows(NodeBusyException.class, 
+                ()->{ deleteNodeService.deleteNode("/test3/m1", user);}
+        );
+
+        Optional<Long> checkSourceId = nodeDao.getNodeId("/test3/m1");
+        assertTrue(checkSourceId.isPresent());
+        
+        Optional<Long> checkSourceIdChild = nodeDao.getNodeId("/test3/m1/m2");
+        assertTrue(checkSourceIdChild.isPresent());        
+    }
+    
+    
+    @Test
+    public void deleteStopOnPermissionDeniedTest() {
+        User user = mock(User.class);
+        when(user.getName()).thenReturn("user-pippo");
+
+        Optional<Long> sourceId = nodeDao.getNodeId("/test3/m1");
+        assertTrue(sourceId.isPresent());
+        Optional<Long> childId = nodeDao.getNodeId("/test3/m1/m2");
+        assertTrue(childId.isPresent());
+      
+        // Delete
+        assertThrows(PermissionDeniedException.class, 
+                ()->{ deleteNodeService.deleteNode("/test3/m1", user);}
+        );
+
+        Optional<Long> checkSourceId = nodeDao.getNodeId("/test3/m1");
+        assertTrue(checkSourceId.isPresent());
+        
+        Optional<Long> checkSourceIdChild = nodeDao.getNodeId("/test3/m1/m2");
+        assertTrue(checkSourceIdChild.isPresent());        
+    }
+
+}
diff --git a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
index 862b598461b5f1a2e53485c61fbcee82c5873fd8..8285f528f2e6de1b2250afff775fbb9a11f65888 100644
--- a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
+++ b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java
@@ -50,7 +50,6 @@ public class NodeDAOTest {
         ReflectionTestUtils.setField(dao, "authority", AUTHORITY);
     }
 
-
     @Test
     public void testCreateNode() {
         DataNode dataNode = new DataNode();
@@ -275,6 +274,27 @@ public class NodeDAOTest {
 
     }
 
+    @Test
+    public void testSetImmutable() {
+        Optional<Long> optId = dao.getNodeId("/test3/m1");
+        assertTrue(optId.isPresent());
+
+        assertFalse(dao.isBranchImmutable(optId.get()));
+
+        dao.setBranchImmutable(optId.get(), true);
+
+        assertTrue(dao.isBranchImmutable(optId.get()));
+
+        Optional<Long> childId = dao.getNodeId("/test3/m1/m2");
+        assertTrue(childId.isPresent());
+
+        assertTrue(dao.isBranchImmutable(childId.get()));
+
+        dao.setBranchImmutable(optId.get(), false);
+
+        assertFalse(dao.isBranchBusy(optId.get()));
+        assertFalse(dao.isBranchBusy((childId.get())));
+    }
 
     @Test
     public void testMoveNodeBranch() {