diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 73da1815ea08528e216196746a390d7132a9e129..19f1490b655ad41acc2e7c184b480166fa456f76 100755
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,2 +1,5 @@
 include:
-  - remote: https://git-cdn.e15r.co/gitlab/ci/wordpress/-/raw/main/plugins/default.yml
+  - remote: https://git-cdn.e15r.co/gitlab/ci/wordpress/-/raw/main/plugins/templates/variables.yml
+  - remote: https://git-cdn.e15r.co/gitlab/ci/wordpress/-/raw/main/plugins/templates/cache.yml
+  - remote: https://git-cdn.e15r.co/gitlab/ci/wordpress/-/raw/main/plugins/templates/before-script-redis.yml
+  - remote: https://git-cdn.e15r.co/gitlab/ci/wordpress/-/raw/main/plugins/templates/matrix-redis.yml
diff --git a/README.md b/README.md
index 1980ae69c3476175f90dc248c85de6de149f016e..f4f8f1822982e7fee2fa17305ff9e93899ef3da7 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Install and activate this plugin, then deactivate the old plugin. Both plugins c
 ## Changelog ##
 
 ### 0.2 ###
-* Rename plugin to `Redis User Session Storage` to submit to WordPress.org plugins repository.
+* Rename plugin to `Redis User Session Storage` to comply with WordPress.org plugin-naming requirements.
 * Allow two versions of this plugin to co-exist safely to support seamless migration.
 * Changes plugin class name to `Redis_User_Session_Storage` from `WP_Redis_User_Session_Storage`.
 
diff --git a/inc/class-plugin.php b/inc/class-plugin.php
index e6b2e07f009c3e62f3ea83ebe154317e702623a8..942b9c377fab510f4c74bfab59820acd662e3a72 100644
--- a/inc/class-plugin.php
+++ b/inc/class-plugin.php
@@ -104,9 +104,6 @@ class Plugin extends WP_Session_Tokens {
 	/**
 	 * Get all sessions of a user.
 	 *
-	 * @since 0.1
-	 * @access protected
-	 *
 	 * @return array Sessions of a user.
 	 */
 	protected function get_sessions() {
@@ -146,9 +143,6 @@ class Plugin extends WP_Session_Tokens {
 	/**
 	 * Retrieve a session by its verifier (token hash).
 	 *
-	 * @since 0.1
-	 * @access protected
-	 *
 	 * @param string $verifier Verifier of the session to retrieve.
 	 * @return array|null The session, or null if it does not exist
 	 */
@@ -165,9 +159,6 @@ class Plugin extends WP_Session_Tokens {
 	/**
 	 * Update a session by its verifier.
 	 *
-	 * @since 0.1
-	 * @access protected
-	 *
 	 * @param string $verifier Verifier of the session to update.
 	 * @param array  $session  Optional. Session. Omitting this argument destroys the session.
 	 */
@@ -186,9 +177,6 @@ class Plugin extends WP_Session_Tokens {
 	/**
 	 * Update a user's sessions in Redis.
 	 *
-	 * @since 0.1
-	 * @access protected
-	 *
 	 * @param array $sessions Sessions.
 	 */
 	protected function update_sessions( $sessions ) {
@@ -196,10 +184,6 @@ class Plugin extends WP_Session_Tokens {
 			return;
 		}
 
-		if ( ! has_filter( 'attach_session_information' ) ) {
-			$sessions = wp_list_pluck( $sessions, 'expiration' );
-		}
-
 		$key = $this->get_key();
 
 		if ( $sessions ) {
@@ -212,9 +196,6 @@ class Plugin extends WP_Session_Tokens {
 	/**
 	 * Destroy all session tokens for a user, except a single session passed.
 	 *
-	 * @since 0.1
-	 * @access protected
-	 *
 	 * @param string $verifier Verifier of the session to keep.
 	 */
 	protected function destroy_other_sessions( $verifier ) {
@@ -224,9 +205,6 @@ class Plugin extends WP_Session_Tokens {
 
 	/**
 	 * Destroy all session tokens for a user.
-	 *
-	 * @since 0.1
-	 * @access protected
 	 */
 	protected function destroy_all_sessions() {
 		$this->update_sessions( array() );
@@ -235,10 +213,6 @@ class Plugin extends WP_Session_Tokens {
 	/**
 	 * Destroy all session tokens for all users.
 	 *
-	 * @since 0.1
-	 * @access public
-	 * @static
-	 *
 	 * @return bool
 	 */
 	public static function drop_sessions() {
@@ -248,9 +222,6 @@ class Plugin extends WP_Session_Tokens {
 	/**
 	 * Empty database, clearing all tokens.
 	 *
-	 * @since 0.2
-	 * @access protected
-	 *
 	 * @return bool
 	 */
 	protected function flush_redis_db() {
@@ -258,10 +229,7 @@ class Plugin extends WP_Session_Tokens {
 	}
 
 	/**
-	 * Build key for current user
-	 *
-	 * @since 0.1
-	 * @access protected
+	 * Build key for current user.
 	 *
 	 * @return string
 	 */
diff --git a/languages/redis-user-session-storage.pot b/languages/redis-user-session-storage.pot
index 0eb5f2db532cb812d135870b68889897a138b321..75009fa459e37dd8cd9cdc53aab94cd1bd41ccce 100644
--- a/languages/redis-user-session-storage.pot
+++ b/languages/redis-user-session-storage.pot
@@ -5,7 +5,7 @@ msgstr ""
 "Project-Id-Version: Redis User Session Storage 0.2\n"
 "Report-Msgid-Bugs-To: "
 "https://wordpress.org/support/plugin/redis-user-session-storage\n"
-"POT-Creation-Date: 2022-07-09 03:45:24+00:00\n"
+"POT-Creation-Date: 2022-07-09 20:23:47+00:00\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=utf-8\n"
 "Content-Transfer-Encoding: 8bit\n"
diff --git a/phpcs.xml b/phpcs.xml
index e41bee18e8bcd39874a217fd866ec735c07980f2..04859b2ff96c6f715514f82f798e6015db5166e6 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -32,7 +32,7 @@
 	<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
 		<properties>
 			<!-- Value: replace the function, class, and variable prefixes used. Separate multiple prefixes with a comma. -->
-			<property name="prefixes" type="array" value="redis_user_session_storage,wp_redis_user_session_storage"/>
+			<property name="prefixes" type="array" value="redis_user_session_storage,wp_redis_user_session"/>
 		</properties>
 	</rule>
 	<rule ref="WordPress.WP.I18n">
diff --git a/phpunit.xml b/phpunit.xml
index 16a39027e72be2cf0a2656056074b6e6ed818be1..a76c2f2178cf14a55b734a9de28be696aa17a5b9 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -8,9 +8,9 @@
 	convertWarningsToExceptions="true"
 	>
 	<testsuites>
-		<testsuite>
-			<directory prefix="test-" suffix=".php">./tests/</directory>
-			<exclude>./tests/test-sample.php</exclude>
+		<testsuite name="wpruss">
+			<directory suffix=".php">./tests/</directory>
+			<exclude>./tests/bootstrap.php</exclude>
 		</testsuite>
 	</testsuites>
 </phpunit>
diff --git a/readme.txt b/readme.txt
index 3ec3b397dcfc1475ae433f23544b0b8f1389b8a1..59aafad0e400d5c5326702d247ef4b802da826a4 100644
--- a/readme.txt
+++ b/readme.txt
@@ -45,7 +45,7 @@ Install and activate this plugin, then deactivate the old plugin. Both plugins c
 == Changelog ==
 
 = 0.2 =
-* Rename plugin to `Redis User Session Storage` to submit to WordPress.org plugins repository.
+* Rename plugin to `Redis User Session Storage` to comply with WordPress.org plugin-naming requirements.
 * Allow two versions of this plugin to co-exist safely to support seamless migration.
 * Changes plugin class name to `Redis_User_Session_Storage` from `WP_Redis_User_Session_Storage`.
 
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index f838a17324fb9d11ace8eb00e2902b3cd4b8acfd..b8985bb10f91380244117b3be4a452a5cdd2fc93 100755
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -29,3 +29,8 @@ tests_add_filter( 'muplugins_loaded', 'redis_user_session_storage_tests_manually
 
 // Start up the WP testing environment.
 require $redis_user_session_storage . '/includes/bootstrap.php';
+
+// Set Redis host for CI.
+if ( ! defined( 'WP_REDIS_USER_SESSION_HOST' ) ) {
+	define( 'WP_REDIS_USER_SESSION_HOST', 'redis' );
+}
diff --git a/tests/inc/class-test-plugin.php b/tests/inc/class-test-plugin.php
new file mode 100755
index 0000000000000000000000000000000000000000..c92a1cccbbac4d894191ab844bc61eba16745497
--- /dev/null
+++ b/tests/inc/class-test-plugin.php
@@ -0,0 +1,430 @@
+<?php
+/**
+ * Test plugin features.
+ *
+ * @package Redis_User_Session_Storage
+ */
+
+namespace Redis_User_Session_Storage\Tests\Inc;
+
+use Redis;
+use Redis_User_Session_Storage\Plugin;
+use ReflectionClass;
+use WP_Session_Tokens;
+use WP_UnitTestCase;
+
+/**
+ * Tests for main plugin class.
+ *
+ * @coversDefaultClass \Redis_User_Session_Storage\Plugin
+ */
+class Test_Plugin extends WP_UnitTestCase {
+	/**
+	 * Clear stored sessions after each test, as factory can create user with
+	 * same ID as previous test.
+	 */
+	public function tear_down() {
+		parent::tear_down();
+
+		$this->_invoke_method( 0, 'flush_redis_db' );
+	}
+
+	/**
+	 * Test construction.
+	 *
+	 * @covers ::__construct()
+	 * @return void
+	 */
+	public function test__construct() {
+		$user_id = $this->factory->user->create();
+		$object  = new Plugin( $user_id );
+
+		$this->assertInstanceOf(
+			WP_Session_Tokens::class,
+			$object,
+			'Failed to assert that plugin class is an instance of `WP_Session_Tokens`.'
+		);
+
+		$this->assertEquals( 'wpruss', $object->prefix );
+
+		$this->assertTrue(
+			$this->_get_property( $user_id, 'redis_connected' ),
+			'Failed to assert that Redis is connected.'
+		);
+
+		$this->assertInstanceOf(
+			Redis::class,
+			$this->_get_property( $user_id, 'redis' ),
+			'Failed to assert that Redis client is an instance of `Redis`.'
+		);
+	}
+
+	/**
+	 * Test `get_sessions()` method.
+	 *
+	 * @covers ::get_sessions()
+	 * @return void
+	 */
+	public function test_get_sessions() {
+		$user_id = $this->factory->user->create();
+		$plugin  = new Plugin( $user_id );
+
+		$this->assertEmpty(
+			$this->_invoke_method( $user_id, 'get_sessions' ),
+			'Failed to assert that no sessions are returned before user logs in.'
+		);
+
+		$plugin->create( time() + 60 );
+
+		$this->assertNotEmpty(
+			$this->_invoke_method( $user_id, 'get_sessions' ),
+			'Failed to assert that session token is stored in Redis.'
+		);
+	}
+
+	/**
+	 * Test `prepare_session()` method.
+	 *
+	 * @covers ::prepare_session()
+	 * @return void
+	 */
+	public function test_prepare_session() {
+		$this->assertEquals(
+			array(
+				'expiration' => 1,
+			),
+			$this->_invoke_method(
+				0,
+				'prepare_session',
+				array(
+					1,
+				)
+			),
+			'Failed to assert that session data is transformed as expected.'
+		);
+
+		$test_data = array(
+			'expiration' => 2,
+			'foo'        => 'bar',
+		);
+
+		$this->assertEquals(
+			$test_data,
+			$this->_invoke_method(
+				0,
+				'prepare_session',
+				array(
+					$test_data,
+				)
+			),
+			'Failed to assert that session data is not transformed if it is already prepared.'
+		);
+	}
+
+	/**
+	 * Test `get_session()` method.
+	 *
+	 * @covers ::get_session()
+	 * @return void
+	 */
+	public function test_get_session() {
+		$user_id = $this->factory->user->create();
+		$plugin  = new Plugin( $user_id );
+
+		$this->assertEmpty(
+			$this->_invoke_method(
+				$user_id,
+				'get_session',
+				array(
+					'abcdef0123456789',
+				)
+			),
+			'Failed to assert that arbitrary verifier does not return a session.'
+		);
+
+		$expiration = time() + 60;
+
+		$plugin->create( $expiration );
+		$tokens   = $this->_invoke_method( $user_id, 'get_sessions' );
+		$verifier = array_keys( $tokens )[0];
+
+		$session_data = $this->_invoke_method(
+			$user_id,
+			'get_session',
+			array(
+				$verifier,
+			)
+		);
+
+		$this->assertEquals(
+			$session_data['expiration'],
+			$expiration,
+			'Failed to assert that session expiration is stored in Redis.'
+		);
+	}
+
+	/**
+	 * Test `update_session()` method.
+	 *
+	 * @covers ::update_session()
+	 * @covers ::update_sessions()
+	 * @return void
+	 */
+	public function test_update_session() {
+		$user_id = $this->factory->user->create();
+		$plugin  = new Plugin( $user_id );
+
+		$plugin->create( time() + 60 );
+
+		$sessions = $this->_invoke_method( $user_id, 'get_sessions' );
+		$verifier = array_keys( $sessions )[0];
+
+		$this->assertNotEmpty(
+			$sessions,
+			'Failed to assert that session was created.'
+		);
+
+		$this->_invoke_method(
+			$user_id,
+			'update_session',
+			array(
+				$verifier,
+			)
+		);
+
+		$this->assertEmpty(
+			$this->_invoke_method(
+				$user_id,
+				'get_session',
+				array(
+					$verifier,
+				)
+			),
+			'Failed to assert that session is not destroyed when no session data is provided.'
+		);
+
+		$plugin->create( time() + 60 );
+
+		$sessions = $this->_invoke_method( $user_id, 'get_sessions' );
+		$verifier = array_keys( $sessions )[0];
+		$session_data = array(
+			'expiration' => time() + 60,
+			'foo'        => 'bar',
+		);
+
+		$this->_invoke_method(
+			$user_id,
+			'update_session',
+			array(
+				$verifier,
+				$session_data,
+			)
+		);
+
+		$this->assertEquals(
+			$session_data,
+			$this->_invoke_method(
+				$user_id,
+				'get_session',
+				array(
+					$verifier,
+				)
+			),
+			'Failed to assert that session is updated when session data is provided.'
+		);
+	}
+
+	/**
+	 * Test `destroy_other_sessions()` method.
+	 *
+	 * @covers ::destroy_other_sessions()
+	 * @return void
+	 */
+	public function test_destroy_other_sessions() {
+		$user_id = $this->factory->user->create();
+		$plugin  = new Plugin( $user_id );
+
+		$plugin->create( time() + 60 );
+		$plugin->create( time() + 120 );
+		$plugin->create( time() + 180 );
+
+		$sessions = $this->_invoke_method( $user_id, 'get_sessions' );
+
+		$this->assertCount(
+			3,
+			$sessions,
+			'Failed to assert that multiple sessions were created.'
+		);
+
+		$verifier = array_keys( $sessions )[0];
+
+		$this->_invoke_method(
+			$user_id,
+			'destroy_other_sessions',
+			array(
+				$verifier,
+			)
+		);
+
+		$this->assertCount(
+			1,
+			$this->_invoke_method(
+				$user_id,
+				'get_sessions'
+			),
+			'Failed to assert that other sessions are destroyed.'
+		);
+	}
+
+	/**
+	 * Test `destroy_all_sessions()` method.
+	 *
+	 * @covers ::destroy_all_sessions()
+	 * @return void
+	 */
+	public function test_destroy_all_sessions() {
+		$user_id = $this->factory->user->create();
+		$plugin  = new Plugin( $user_id );
+
+		$plugin->create( time() + 60 );
+		$plugin->create( time() + 120 );
+		$plugin->create( time() + 180 );
+
+		$sessions = $this->_invoke_method( $user_id, 'get_sessions' );
+
+		$this->assertCount(
+			3,
+			$this->_invoke_method(
+				$user_id,
+				'get_sessions'
+			),
+			'Failed to assert that multiple sessions were created.'
+		);
+
+		$this->_invoke_method(
+			$user_id,
+			'destroy_all_sessions'
+		);
+
+		$this->assertEmpty(
+			$this->_invoke_method(
+				$user_id,
+				'get_sessions'
+			),
+			'Failed to assert that all sessions were destroyed.'
+		);
+	}
+
+	/**
+	 * Test `drop_sessions()` method.
+	 *
+	 * @covers ::drop_sessions()
+	 * @covers ::flush_redis_db()
+	 * @return void
+	 */
+	public function test_drop_sessions() {
+		$user_1        = $this->factory->user->create();
+		$plugin_user_1 = new Plugin( $user_1 );
+		$user_2        = $this->factory->user->create();
+		$plugin_user_2 = new Plugin( $user_2 );
+
+		$plugin_user_1->create( time() + 60 );
+		$plugin_user_1->create( time() + 120 );
+		$plugin_user_1->create( time() + 180 );
+		$plugin_user_2->create( time() + 60 );
+		$plugin_user_2->create( time() + 120 );
+		$plugin_user_2->create( time() + 180 );
+
+		$this->assertCount(
+			3,
+			$this->_invoke_method(
+				$user_1,
+				'get_sessions'
+			),
+			'Failed to assert that multiple sessions were created for user 1.'
+		);
+
+		$this->assertCount(
+			3,
+			$this->_invoke_method(
+				$user_2,
+				'get_sessions'
+			),
+			'Failed to assert that multiple sessions were created for user 2.'
+		);
+
+		$this->_invoke_method(
+			$user_1,
+			'flush_redis_db'
+		);
+
+		$this->assertEmpty(
+			$this->_invoke_method(
+				$user_1,
+				'get_sessions'
+			),
+			'Failed to assert that sessions were destroyed for user 1.'
+		);
+
+		$this->assertEmpty(
+			$this->_invoke_method(
+				$user_2,
+				'get_sessions'
+			),
+			'Failed to assert that sessions were destroyed for user 2.'
+		);
+	}
+
+	/**
+	 * Test `get_key()` method.
+	 *
+	 * @covers ::get_key()
+	 * @return void
+	 */
+	public function test_get_key() {
+		$user_id = $this->factory->user->create();
+		$plugin  = new Plugin( $user_id );
+
+		$this->assertEquals(
+			$plugin->prefix . ':' . $user_id,
+			$this->_invoke_method( $user_id, 'get_key' )
+		);
+	}
+
+	/**
+	 * Invoke a non-public class method.
+	 *
+	 * @param int    $user_id     WP User ID.
+	 * @param string $method_name Method name.
+	 * @param array  $args        Method arguments.
+	 * @return mixed
+	 */
+	protected function _invoke_method(
+		$user_id,
+		$method_name,
+		$args = array()
+	) {
+		$object     = new Plugin( $user_id );
+		$reflection = new ReflectionClass( $object );
+		$method     = $reflection->getMethod( $method_name );
+		$method->setAccessible( true );
+
+		return $method->invokeArgs( $object, $args );
+	}
+
+	/**
+	 * Get value of non-public property.
+	 *
+	 * @param int    $user_id       WP User ID.
+	 * @param string $property_name Property name.
+	 * @return mixed
+	 */
+	protected function _get_property( $user_id, $property_name ) {
+		$object     = new Plugin( $user_id );
+		$reflection = new ReflectionClass( $object );
+		$property   = $reflection->getProperty( $property_name );
+		$property->setAccessible( true );
+
+		return $property->getValue( $object );
+	}
+}
diff --git a/tests/test-sample.php b/tests/test-sample.php
deleted file mode 100755
index c8f05ed5bd8bfe159a2d1b5efe3a96e2dbf30198..0000000000000000000000000000000000000000
--- a/tests/test-sample.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-/**
- * Class SampleTest
- *
- * @package Redis_User_Session_Storage
- */
-
-/**
- * Sample test case.
- */
-class SampleTest extends WP_UnitTestCase {
-
-	/**
-	 * A single example test.
-	 */
-	public function test_sample() {
-		// Replace this with some actual testing code.
-		$this->assertTrue( true );
-	}
-}