Blog

  • SOLID Unit Tests

    Set of Unit Tests for the previous SOLID Examples post.

    Single Responsibility Principle (SRP)

    Unit tests for the Authenticator class from the Single Responsibility Principle example from the previous post.

    use PHPUnit\Framework\TestCase;
    
    class AuthenticatorTest extends TestCase
    {
      public function testAuthenticateWithValidCredentials()
      {
          // Arrange
          $credentialsValidator = $this->createMock(CredentialsValidator::class);
          $credentialsValidator->method('isValid')
              ->willReturn(true);
          $sessionGenerator = $this->createMock(SessionGenerator::class);
          $sessionGenerator->method('generate')
              ->willReturn('abcdef123456');
          $cookieManager = $this->createMock(CookieManager::class);
          $cookieManager->expects($this->once())
              ->method('set')
              ->with('sessionToken', 'abcdef123456');
          $redirector = $this->createMock(Redirector::class);
          $redirector->expects($this->once())
              ->method('redirect')
              ->with('/dashboard');
          $authenticator = new Authenticator(
              $credentialsValidator,
              $sessionGenerator,
              $cookieManager,
              $redirector
          );
          // Act
          $authenticator->authenticate('user', 'pass');
    
          // Assert
          // Ensure that the user is redirected to the dashboard
      }
      public function testAuthenticateWithInvalidCredentials()
      {
        // Arrange
        $credentialsValidator = $this->createMock(CredentialsValidator::class);
        $credentialsValidator->method('isValid')->willReturn(false);
        $sessionGenerator = $this->createMock(SessionGenerator::class);
        $cookieManager = $this->createMock(CookieManager::class);
        $redirector = $this->createMock(Redirector::class);
        $redirector->expects($this->once())
          ->method('redirect')
          ->with('/login?error=invalid_credentials');
        $authenticator = new Authenticator(
          $credentialsValidator,
          $sessionGenerator,
          $cookieManager,
          $redirector
        );
    
        // Act
        $authenticator->authenticate('user', 'pass');
        // Assert
        // Ensure that the user is redirected to the login page with an error message
      }
    }
    

    These tests use PHPUnit to create mock objects for the CredentialsValidator, SessionGenerator, CookieManager, and Redirector classes. The testAuthenticateWithValidCredentials() test ensures that a user is redirected to the dashboard when they enter valid login credentials, while the testAuthenticateWithInvalidCredentials() test ensures that a user is redirected to the login page with an error message when they enter invalid login credentials.

    These tests cover the expected behaviour of the Authenticator class and help to ensure that it adheres to the Single Responsibility Principle.

    Open/Closed Principle (OCP)

    Unit tests for the VendorDiscountShoppingCart class from the Development Notes Solid Examples post for Open/Closed Principle using PHPUnit.

    use PHPUnit\Framework\TestCase;
    
    class VendorDiscountShoppingCartTest extends TestCase
    {
        public function testGetTotalWithoutDiscount()
        {
            // Arrange
            $item1 = new Item('item1', 10.00, 1, 1);
            $item2 = new Item('item2', 20.00, 1, 2);
            $shoppingCart = new ShoppingCart();
            $shoppingCart->addItem($item1);
            $shoppingCart->addItem($item2);
            $vendorDiscountShoppingCart = new VendorDiscountShoppingCart($shoppingCart);
    
            // Act
            $total = $vendorDiscountShoppingCart->getTotal(1);
    
            // Assert
            $this->assertEquals(10.00, $total);
        }
    
        public function testGetTotalWithDiscount()
        {
            // Arrange
            $item1 = new Item('item1', 10.00, 1, 1);
            $item2 = new Item('item2', 10.00, 1, 1);
            $item3 = new Item('item3', 10.00, 1, 1);
            $item4 = new Item('item4', 10.00, 1, 1);
            $item5 = new Item('item5', 10.00, 1, 1);
            $shoppingCart = new ShoppingCart();
            $shoppingCart->addItem($item1);
            $shoppingCart->addItem($item2);
            $shoppingCart->addItem($item3);
            $shoppingCart->addItem($item4);
            $shoppingCart->addItem($item5);
            $vendorDiscountShoppingCart = new VendorDiscountShoppingCart($shoppingCart);
    
            // Act
            $total = $vendorDiscountShoppingCart->getTotal(1);
    
            // Assert
            $this->assertEquals(45.00, $total);
        }
    }
    

    These tests use PHPUnit to create Item objects and add them to a ShoppingCart, which is passed to a VendorDiscountShoppingCart object. The testGetTotalWithoutDiscount() test ensures that the getTotal() method returns the correct total without a discount, while the testGetTotalWithDiscount() test ensures that the getTotal() method returns the correct total with a discount. These tests cover the expected behaviour of the VendorDiscountShoppingCart class and help to ensure that it adheres to the Open/Closed Principle.

    Liskov Substitution Principle (LSP)

    A set of unit tests for the LibraryItem class hierarchy from the Development Notes Solid Examples post for Liskov Substitution Principle (LSP) example using PHPUnit.

    use PHPUnit\Framework\TestCase;
    
    class LibraryItemTest extends TestCase
    {
        public function testBookGetDuration()
        {
            // Arrange
            $book = new Book();
    
            // Act
            $duration = $book->getDuration();
    
            // Assert
            $this->assertEquals(0, $duration);
        }
    
        public function testDVDGetDuration()
        {
            // Arrange
            $dvd = new DVD();
            $dvd->setDuration(90);
    
            // Act
            $duration = $dvd->getDuration();
    
            // Assert
            $this->assertEquals(90, $duration);
        }
    
        public function testAudioBookGetDuration()
        {
            // Arrange
            $audioBook = new AudioBook();
    
            // Act
            $duration = $audioBook->getDuration();
    
            // Assert
            $this->assertEquals(0, $duration);
        }
    }
    

    These tests use PHPUnit to create instances of the Book, DVD, and AudioBook classes, and call the getDuration() method on each object. The testBookGetDuration() test ensures that the getDuration() method returns 0 for a Book object, the testDVDGetDuration() test ensures that the getDuration() method returns the correct duration for a DVD object, and the testAudioBookGetDuration() test ensures that the getDuration() method returns 0 for an AudioBook object. These tests cover the expected behaviour of the LibraryItem class hierarchy and help to ensure that it adheres to the Liskov Substitution Principle (LSP).

    Interface Segregation Principle (ISP)

    Unit tests for the TextEditor and ImageEditor interfaces from the Interface Segregation Principle (ISP) example from SOLID Examples post using PHPUnit.

    use PHPUnit\Framework\TestCase;
    
    class TextEditorTest extends TestCase
    {
        public function testEditText()
        {
            // Arrange
            $textContent = new TextContent('Hello, world!');
            $textEditor = new SimpleTextEditor();
    
            // Act
            $textEditor->editText($textContent);
    
            // Assert
            $this->assertEquals('Hello, world!', $textContent->getText());
        }
    }
    
    class ImageEditorTest extends TestCase
    {
        public function testUploadImage()
        {
            // Arrange
            $imageContent = new ImageContent('example.jpg');
            $imageEditor = new ImageUploader();
            $file = 'example.jpg';
    
            // Act
            $imageEditor->uploadImage($imageContent, $file);
    
            // Assert
            $this->assertEquals('example.jpg', $imageContent->getImage());
        }
    }
    

    These tests use PHPUnit to create instances of the TextContent and ImageContent classes, and instances of the SimpleTextEditor and ImageUploader classes that implement the TextEditor and ImageEditor interfaces, respectively. The testEditText() test ensures that the editText() method of the SimpleTextEditor class edits the text content of a TextContent object as expected, while the testUploadImage() test ensures that the uploadImage() method of the ImageUploader class uploads a new image file to an ImageContent object as expected. These tests cover the expected behaviour of the TextEditor and ImageEditor interfaces and help to ensure that they adhere to the Interface Segregation Principle (ISP).

    Dependency Inversion Principle (DIP)

    An example set of unit tests for the Mailer class and its dependencies from the Dependency Inversion Principle (DIP) example from the SOLID Examples post using PHPUnit.

    use PHPUnit\Framework\TestCase;
    
    class MailerTest extends TestCase
    {
        public function testSendEmailWithSmtpTransport()
        {
            // Arrange
            $transport = $this->createMock(MailerTransport::class);
            $transport->expects($this->once())
                ->method('send')
                ->with('user@example.com', 'Welcome!', 'Thanks for signing up.');
            $container = new Container();
            $container->bind(MailerTransport::class, SmtpTransport::class);
            $mailer = $container->make(Mailer::class, [$transport]);
    
            // Act
            $mailer->sendEmail('user@example.com', 'Welcome!', 'Thanks for signing up.');
        }
    
        public function testSendEmailWithMailgunTransport()
        {
            // Arrange
            $transport = $this->createMock(MailerTransport::class);
            $transport->expects($this->once())
                ->method('send')
                ->with('user@example.com', 'Welcome!', 'Thanks for signing up.');
            $container = new Container();
            $container->bind(MailerTransport::class, MailgunTransport::class);
            $mailer = $container->make(Mailer::class, [$transport]);
    
            // Act
            $mailer->sendEmail('user@example.com', 'Welcome!', 'Thanks for signing up.');
        }
    }
    

    These tests use PHPUnit to create mock objects for the MailerTransport interface and the Container class. The testSendEmailWithSmtpTransport() test ensures that the Mailer class sends an email using the SmtpTransport implementation of the MailerTransport interface, while the testSendEmailWithMailgunTransport() test ensures that the Mailer class sends an email using the MailgunTransport implementation of the MailerTransport interface. These tests cover the expected behaviour of the Mailer class and its dependencies and help to ensure that it adheres to the Dependency Inversion Principle (DIP).

  • SOLID Examples

    Single Responsibility Principle (SRP)

    Suppose you are building a web application that allows users to sign up and log in to their accounts. To handle user authentication, you might create a class called Authenticator that has the following responsibilities:

    Validate the user’s login credentials.
    Generate a session token if the credentials are valid.
    Store the session token in a cookie.
    Redirect the user to the appropriate page.
    However, this class violates SRP because it has more than one responsibility.

    A better approach would be to split these responsibilities into separate classes:

    A CredentialsValidator class that validates the user’s login credentials.
    A SessionGenerator class that generates a session token.
    A CookieManager class that stores the session token in a cookie.
    A Redirector class that redirects the user to the appropriate page.


    Each class has a single responsibility and can be tested and maintained separately.

    You could then create an Authenticator class that depends on these classes and orchestrates the authentication process:

    class Authenticator
    {
        private $credentialsValidator;
        private $sessionGenerator;
        private $cookieManager;
        private $redirector;
    
        public function __construct(
            CredentialsValidator $credentialsValidator,
            SessionGenerator $sessionGenerator,
            CookieManager $cookieManager,
            Redirector $redirector
        ) {
            $this->credentialsValidator = $credentialsValidator;
            $this->sessionGenerator = $sessionGenerator;
            $this->cookieManager = $cookieManager;
            $this->redirector = $redirector;
        }
    
        public function authenticate($username, $password)
        {
            if ($this->credentialsValidator->isValid($username, $password)) {
                $sessionToken = $this->sessionGenerator->generate();
                $this->cookieManager->set('sessionToken', $sessionToken);
                $this->redirector->redirect('/dashboard');
            } else {
                $this->redirector->redirect('/login?error=invalid_credentials');
            }
        }
    }
    

    Unit Tests for SRP Example

    This approach separates concerns and makes the code easier to test and maintain.

    Open/Closed Principle (OCP)

    Suppose you are building an e-commerce platform that allows customers to purchase products from multiple vendors. You might have a class called ShoppingCart that calculates the total cost of the items in the cart:

    class ShoppingCart
    {
        private $items = [];
    
        public function addItem($item)
        {
            $this->items[] = $item;
        }
    
        public function getTotal()
        {
            $total = 0;
            foreach ($this->items as $item) {
                $total += $item->getPrice();
            }
            return $total;
        }
    }

    However, you now want to offer a discount to customers who purchase a certain number of items from a specific vendor. To implement this feature, you might be tempted to modify the ShoppingCart class to include a vendor-specific discount calculation:

    class ShoppingCart
    {
        private $items = [];
    
        public function addItem($item)
        {
            $this->items[] = $item;
        }
    
        public function getTotal($vendorId)
        {
            $total = 0;
            $vendorItems = [];
            foreach ($this->items as $item) {
                if ($item->getVendorId() == $vendorId) {
                    $vendorItems[] = $item;
                } else {
                    $total += $item->getPrice();
                }
            }
            if (count($vendorItems) >= 5) {
                $vendorTotal = 0;
                foreach ($vendorItems as $item) {
                    $vendorTotal += $item->getPrice();
                }
                $total += $vendorTotal * 0.9; // 10% discount
            } else {
                $total += $vendorTotal;
            }
            return $total;
        }
    }
    

    However, this violates the Open/Closed Principle (OCP) because the ShoppingCart class is now open for modification. Instead, you should create a new class that extends ShoppingCart and adds the vendor-specific discount calculation:

    class VendorDiscountShoppingCart extends ShoppingCart
    {
        public function getTotal($vendorId)
        {
            $total = parent::getTotal();
            $vendorItems = [];
            foreach ($this->items as $item) {
                if ($item->getVendorId() == $vendorId) {
                    $vendorItems[] = $item;
                }
            }
            if (count($vendorItems) >= 5) {
                $vendorTotal = 0;
                foreach ($vendorItems as $item) {
                    $vendorTotal += $item->getPrice();
                }
                $total -= $vendorTotal * 0.1; // 10% discount
            }
            return $total;
        }
    }
    

    Unit Tests for OCP Example

    This approach extends the ShoppingCart class and adds the vendor-specific discount calculation without modifying the existing code. The VendorDiscountShoppingCart class is closed for modification but open for extension, allowing you to add new features without breaking existing code.

    Liskov Substitution Principle (LSP)

    Suppose you are building a library management system with a LibraryItem class hierarchy. The LibraryItem class has two derived classes: Book and DVD. Each of these classes has a method called getDuration(), which returns the duration of the item in minutes:

    abstract class LibraryItem
    {
        // ...
        abstract public function getDuration(): int;
        // ...
    }
    
    class Book extends LibraryItem
    {
        // ...
        public function getDuration(): int
        {
            return 0; // Books don't have a duration
        }
        // ...
    }
    
    class DVD extends LibraryItem
    {
        // ...
        public function getDuration(): int
        {
            return $this->duration;
        }
        // ...
    }
    
    

    Suppose you want to add a new type of library item called AudioBook. This class should also have a getDuration() method that returns the duration of the item in minutes. However, because audiobooks don’t have a fixed duration as DVDs do, the getDuration() method should always return zero:

    class AudioBook extends LibraryItem
    {
        // ...
        public function getDuration(): int
        {
            return 0; // Audio books don't have a fixed duration
        }
        // ...
    }
    

    Unit Tests for LSP Example

    This class adheres to the Liskov Substitution Principle (LSP) because it can be substituted for its base class LibraryItem without affecting the correctness of the program. Clients of the LibraryItem class hierarchy can safely call the getDuration() method on an AudioBook object without worrying about the implementation details.

    By adhering to the LSP, you can ensure that your code is flexible and extensible and that new derived classes can be added without breaking existing code.

    Interface Segregation Principle (ISP)

    Suppose you are building a CMS (Content Management System) that has a Content class hierarchy. The Content class has a method called render() that renders the content as HTML. You also have a class called Editor that allows users to edit content.

    Initially, you might define an Editor interface that has a single method called edit(), which takes a Content object as a parameter:

    interface Editor
    {
        public function edit(Content $content);
    }
    

    However, this violates the Interface Segregation Principle (ISP) because the Editor interface is too broad. Not all types of Content objects can be edited in the same way. For example, a TextContent object might allow the user to edit the text content directly, while an ImageContent object might require the user to upload a new image file.

    A better approach would be to define more specific interfaces for each type of Content object:

    interface TextEditor
    {
        public function editText(TextContent $content);
    }
    
    interface ImageEditor
    {
        public function uploadImage(ImageContent $content, $file);
    }
    
    

    Now you can create concrete classes that implement these interfaces and define how to edit each type of Content object:

    class SimpleTextEditor implements TextEditor
    {
        public function editText(TextContent $content)
        {
            // Allow the user to edit the text content directly
        }
    }
    
    class ImageUploader implements ImageEditor
    {
        public function uploadImage(ImageContent $content, $file)
        {
            // Allow the user to upload a new image file
        }
    }
    

    Unit Tests for ISP Example

    This approach adheres to the ISP by defining small, focused interfaces that clients can use to interact with your classes, rather than large, monolithic interfaces that contain a lot of unnecessary methods. Clients of the Editor class hierarchy can now depend on the specific interfaces that they need, rather than depending on a broad interface that exposes unnecessary methods.

    Dependency Inversion Principle (DIP)

    Suppose you are building a web application that uses a Mailer class to send email notifications to users. The Mailer class depends on a concrete implementation of a MailerTransport interface, which defines how the mailer sends emails:

    interface MailerTransport
    {
        public function send(string $to, string $subject, string $body);
    }
    
    class SmtpTransport implements MailerTransport
    {
        public function send(string $to, string $subject, string $body)
        {
            // Use SMTP to send the email
        }
    }
    
    class MailgunTransport implements MailerTransport
    {
        public function send(string $to, string $subject, string $body)
        {
            // Use the Mailgun API to send the email
        }
    }
    
    class Mailer
    {
        private $transport;
    
        public function __construct(MailerTransport $transport)
        {
            $this->transport = $transport;
        }
    
        public function sendEmail(string $to, string $subject, string $body)
        {
            $this->transport->send($to, $subject, $body);
        }
    }
    
    

    Initially, the Mailer class depended on concrete implementations of the MailerTransport interface, such as SmtpTransport and MailgunTransport. However, this violates the Dependency Inversion Principle (DIP) because it creates a tight coupling between the Mailer class and its dependencies.

    To adhere to the DIP, you should invert the dependencies so that the Mailer class depends on abstractions, rather than concretions. One way to achieve this is by using a dependency injection container, which manages the dependencies for you:

    interface MailerTransport
    {
        public function send(string $to, string $subject, string $body);
    }
    
    class SmtpTransport implements MailerTransport
    {
        public function send(string $to, string $subject, string $body)
        {
            // Use SMTP to send the email
        }
    }
    
    class MailgunTransport implements MailerTransport
    {
        public function send(string $to, string $subject, string $body)
        {
            // Use the Mailgun API to send the email
        }
    }
    
    class Mailer
    {
        private $transport;
    
        public function __construct(MailerTransport $transport)
        {
            $this->transport = $transport;
        }
    
        public function sendEmail(string $to, string $subject, string $body)
        {
            $this->transport->send($to, $subject, $body);
        }
    }
    
    $container = new Container();
    $container->bind(MailerTransport::class, SmtpTransport::class); // or MailgunTransport::class
    $mailer = $container->make(Mailer::class);
    $mailer->sendEmail('user@example.com', 'Welcome!', 'Thanks for signing up.');
    

    Unit Tests for DIP Example

    Now the Mailer class depends on the MailerTransport interface, which is an abstraction, rather than a concrete implementation. The Container class manages the dependencies for you, allowing you to swap out implementations without modifying the Mailer class.

    This approach adheres to the DIP by decoupling the Mailer class from its dependencies and making it more flexible and maintainable.

  • SOLID Principles

    As a PHP developer, you can benefit from using SOLID principles to write better, more maintainable code.

    SOLID stands for:

    Single Responsibility Principle (SRP)
    Open/Closed Principle (OCP)
    Liskov Substitution Principle (LSP)
    Interface Segregation Principle (ISP)
    Dependency Inversion Principle (DIP)

    Here’s a brief explanation of each principle and how they can be applied in PHP development:

    Single Responsibility Principle (SRP)

    This principle states that a class should have only one responsibility. In other words, a class should do one thing and do it well. This helps to keep your code organized and maintainable. In PHP, you can achieve this by creating classes that have a clear and concise purpose, and by avoiding bloated classes that try to do too much.

    Solid Example for Single Responsibility Principle

    Open/Closed Principle (OCP)

    This principle states that a class should be open for extension but closed for modification. This means that you should be able to add new functionality to a class without modifying its existing code. In PHP, you can achieve this by using interfaces and abstract classes to define the contract that classes must adhere to, and by using inheritance and composition to add new functionality.

    Liskov Substitution Principle (LSP)

    This principle states that a derived class should be able to be substituted for its base class without affecting the correctness of the program. In other words, if you have a class hierarchy, you should be able to replace any instance of a base class with an instance of its derived class, without breaking anything. In PHP, you can achieve this by ensuring that derived classes don’t violate the contracts of their base classes.

    Interface Segregation Principle (ISP)

    This principle states that clients should not be forced to depend on interfaces they do not use. In other words, you should define small, focused interfaces that clients can use to interact with your classes, rather than large, monolithic interfaces that contain a lot of unnecessary methods. In PHP, you can achieve this by creating interfaces that define only the methods that are necessary for a particular use case.

    Dependency Inversion Principle (DIP)

    This principle states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions. In other words, you should depend on abstractions rather than concretions. This helps to decouple your code and make it more flexible and maintainable. In PHP, you can achieve this by using dependency injection and inversion of control (IoC) containers to manage your object dependencies.

  • Sizing Non-LVM Root Partitions

    Recently, I was faced with a busy production server with a space problem where the root partition was near capacity.

    After some research, I determined that it was possible to add space to the RHEL root partition but felt that this would be risky as I did not have experience making this sort of change.

    Fortunately, I was able to spin up a clone of this system in our QA Environment to test the procedure that I explain below.

    Overview

    • Allocate Disk Space to Virtual Machine In VMWare vSphere
    • Backup Virtual Machine
    • Execute Steps Below
    • Test Services including Backup Process

    Steps

    1. Login(ssh) to Server and Sudo to Root (su)
      ssh server-name
      sudo su

    2. List Disk Partitions
      fdisk -l

      Disk /dev/sda: 1319.4GB, 1319413952512 bytes
      255 heads, 63 sectors/track, 160509 cylinders

      Disk identifier: 0x00000000

      Device Boot Start End Blocks Id System
      /dev/sda1 1 8355 67108864 82 Linux swap / Solaris
      /dev/sda2 * 8355 133675 1006631936 83 Linux

    3. Determine unique device Identifiers
      blkid

      /dev/sda2: 970ab9b7-e71a-45da-907c-1b4005c1c8a9 /
      /dev/sda1: 43d83161-4d58-4cb5-8d39-4511f693cc87 swap

    4. Check mount list
      cat /etc/fstab

    5. Modify /dev/sda Disk using fdisk command
      fdisk /dev/sda

    6. In fdisk – List, delete, partition 2
      p d 2

    7. List, Add New Partition 2, Start 8355, End 160509, write partition table and quit
      p n p 2 8355 160509 W

    8. Modify /dev/sda Disk partition using fdisk command
      fdisk /dev/sda

      Check Partition ID and change Partition Type to 83 (Linux) If Incorrect.
    9. List, Set, Parition 2, Type 83 (Linux), List and Write Partition Table.
      p t 2 83 p w

    10. Use blkid command to check partition identifier is the same and move on to the next step or change identifier in /etc/fstab file to the new identifier value. *** Please Note *** Failure to change the value in the /etc/fstab file will most likely result in the system not booting.
      blkid

    11. Restart VM, Login, Check Disk Size, Update File System, Partitions, and Disk Size
      init 6
      ssh server-name
      sudo su
      df –h

      Filesystem Size Used Avail Use% Mounted on
      /dev/sda2 945G 756G 142G 85% /
      tmpfs 32G 0 32G 0% /dev/shm

      resize2fs /dev/sda2
      resize2fs 1.41.12 (17-May-2010)
      Filesystem at /dev/sda2 is mounted on /; online resizing required
      old desc_blocks = 60, new_desc_blocks = 73
      Performing an online resize of /dev/sda2 to 305343851 (4k) blocks.
      The filesystem on /dev/sda2 is now 305343851 blocks long.

      fdisk -l

      Disk /dev/sda: 1319.4 GB, 1319413952512 bytes
      255 heads, 63 sectors/track, 160409 cylinders
      Units = cylinders of 16065 * 512 = 8225280 bytes
      Sector size (logical/physical): 512 bytes / 512 bytes
      I/O size (minimum/optimal): 512 bytes / 512 bytes
      Disk identifier: 0x00000000

      Device Boot Start End Blocks Id System
      /dev/sda1 1 8355 67108864 82 Linux swap / Solaris
      /dev/sda2 8355 160409 1221375404+ 83 Linux


      df –h

      Filesystem Size Used Avail Use% Mounted on
      /dev/sda2 1.2T 756G 334G 70% /
      tmpfs 32G 0 32G 0% /dev/shm

    Outcomes

    1. Root Partition Disk Space Increased from 945G to 1.2T by resizing disk.
    2. Database Backups stopped failing
    3. Data migration continued successfully