diff --git a/src/PostgresDriver/CursorException.php b/src/PostgresDriver/CursorException.php
index e5d24d56769e4f3d03bc6fd184943aec4480e061..70e641e5c19a6b8ba1b769eb0464ae21b45a05a0 100644
--- a/src/PostgresDriver/CursorException.php
+++ b/src/PostgresDriver/CursorException.php
@@ -27,36 +27,70 @@ class CursorException extends \LogicException
 		parent::__construct($message, $code, $previous);
 	}
 
-	public static function noDataToFetch()
+	public static function noDataToFetch() : self
 	{
 		return new static(self::MESSAGE_NO_DATA_TO_FETCH);
 	}
 
-	public static function cursorOverflow()
+	public static function cursorOverflow() : self
 	{
 		return new static(self::MESSAGE_OVERFLOW);
 	}
 
-	public static function noDataCursorHasBeenSetToZero()
+	public static function noDataCursorHasBeenSetToZero() : self
 	{
 		return new static(self::MESSAGE_NO_DATA_CURSOR_RESET);
 	}
 
-	public static function cannotRecoverCursorIntoOriginalPosition(\Throwable $previous)
+	public static function cannotRecoverCursorIntoOriginalPosition(\Throwable $previous) : self
 	{
 		return new static(self::MESSAGE_CANNOT_RECOVER_TO_ORIGINAL_POSITION, 0, $previous);
 	}
 
-	public static function cannotMoveToTheEnd()
+	public static function cannotMoveToTheEnd() : self
 	{
 		return new static(self::MESSAGE_CANNOT_REACH_THE_END);
 	}
 
-	public static function untraceableValue($moreInfo)
+	public static function cursorPosition_invalidStartingPoint() : self
+	{
+		return new static("Invalid starting point value. Use constants defined in value object.");
+	}
+
+	public static function cursorPosition_invalidIndexValue(int $value) : self
+	{
+		return new static(
+			sprintf("Index value %i is not valid.", $value)
+		);
+	}
+
+	public static function untraceableValue($moreInfo) : self
 	{
 		return new static(
 			sprintf(self::MESSAGE_UNTRACEABLE_VALUE, $moreInfo)
 		);
 	}
 
+	public static function unexpectedRowCountReturned(int $rowCount) : self
+	{
+		return new static(
+			sprintf("Underlying driver failed. Returned unexpected number (%i) of rows.", $rowCount)
+		);
+	}
+
+	public static function cursorPosition_unknownError() : self
+	{
+		return new static("Moving cursor failed due to unknown error.");
+	}
+
+	public static function noResultsAvailable_hitBeginning() : self
+	{
+		return new static("No results available. You've hit the beginning.");
+	}
+
+	public static function noResultsAvailable_hitEnd() : self
+	{
+		return new static("No results available. You've hit the end.");
+	}
+
 }
\ No newline at end of file
diff --git a/src/PostgresDriver/CursorPosition.php b/src/PostgresDriver/CursorPosition.php
new file mode 100644
index 0000000000000000000000000000000000000000..5745aae40e4f68420c6fefa94fc177a99722b2d6
--- /dev/null
+++ b/src/PostgresDriver/CursorPosition.php
@@ -0,0 +1,96 @@
+<?php declare(strict_types = 1);
+/**
+ * This file is part of mappi/store.
+ */
+
+namespace Grifart\Mappi\Store\PostgresDriver;
+
+/**
+ * Value object for TrackedCursor position
+ *
+ * @link    https://github.com/nicolopignatelli/valueobjects (inspiration)
+ * @package Grifart\Mappi\Store\PostgresDriver
+ */
+final class CursorPosition
+{
+	/** @var CursorPositionOrigin */
+	private $origin;
+
+	/** @var int the cursor position */
+	private $position = 0;
+
+	/**
+	 * @param CursorPositionOrigin $origin   from left or from right?
+	 * @param int                  $position which position
+	 */
+	public function __construct(CursorPositionOrigin $origin, int $position)
+	{
+		$this->setPosition($origin, $position);
+	}
+
+	public static function fromLeft(int $position) : self
+	{
+		return new self(
+			CursorPositionOrigin::get(CursorPositionOrigin::FROM_LEFT),
+			$position
+		);
+	}
+
+	public static function fromRight(int $position) : self
+	{
+		return new self(
+			CursorPositionOrigin::get(CursorPositionOrigin::FROM_RIGHT),
+			$position
+		);
+	}
+
+	public function setPositionFromLeft(int $position)
+	{
+		$this->setPosition(CursorPositionOrigin::get(CursorPositionOrigin::FROM_LEFT), $position);
+	}
+
+	public function setPositionFromRight(int $position)
+	{
+		$this->setPosition(CursorPositionOrigin::get(CursorPositionOrigin::FROM_RIGHT), $position);
+	}
+
+	private function setPosition(CursorPositionOrigin $origin, int $position)
+	{
+		if($position < 0) {
+			throw CursorException::cursorPosition_invalidIndexValue($position);
+		}
+		$this->origin = $origin;
+		$this->position = $position;
+	}
+
+	/**
+	 * @param int $by negative = left; positive = right
+	 */
+	public function movePositionBy(int $by)
+	{
+		$modifier = 1;
+		if ($this->origin->is(CursorPositionOrigin::FROM_RIGHT)) {
+			$modifier = -1;
+		}
+		$this->position += $modifier * $by;
+	}
+
+	/**
+	 * From which side is position counted?
+	 * @return CursorPositionOrigin
+	 */
+	public function getOrigin()
+	{
+		return $this->origin;
+	}
+
+	/**
+	 * Cursor position from origin
+	 * @return int
+	 */
+	public function getPosition()
+	{
+		return $this->position;
+	}
+
+}
\ No newline at end of file
diff --git a/src/PostgresDriver/CursorPositionOrigin.php b/src/PostgresDriver/CursorPositionOrigin.php
new file mode 100644
index 0000000000000000000000000000000000000000..01447b8bcd4052bc30c4a7acea4242930b756a37
--- /dev/null
+++ b/src/PostgresDriver/CursorPositionOrigin.php
@@ -0,0 +1,14 @@
+<?php declare(strict_types = 1);
+/**
+ * This file is part of mappi/store.
+ */
+
+namespace Grifart\Mappi\Store\PostgresDriver;
+
+use MabeEnum\Enum;
+
+class CursorPositionOrigin extends Enum
+{
+	const FROM_LEFT = 0;
+	const FROM_RIGHT   = 1;
+}
diff --git a/src/PostgresDriver/TrackedCursor.php b/src/PostgresDriver/TrackedCursor.php
index 79d3f161fd862d4e6e3e966d82a5dcdd11e8ef3a..4ef618bc330e78b8f00cb4c09abbbdaaac08e43b 100644
--- a/src/PostgresDriver/TrackedCursor.php
+++ b/src/PostgresDriver/TrackedCursor.php
@@ -7,33 +7,38 @@ namespace Grifart\Mappi\Store\PostgresDriver;
 
 use Dibi\Connection;
 
-class TrackedCursor implements ICursor
+class TrackedCursor implements ICursorDriver
 {
-
 	/** @var int */
 	private $total;
 
 	/**
-	 * null-based index of current cursor state
-	 * @var int
+	 * @var CursorPosition
 	 */
-	private $position = 0;
+	private $position;
 
-	
 	/** @var ICursor */
 	private $cursor;
 
+	/** @var Connection */
+	private $connection;
+
 	/**
-	 * @param ICursor $cursor Cursor in initial state (index=0)
-	 * Tip: if you are not sure that cursor will be in initial state, call ->moveToBeginning() after initialization.
+	 * Tip: if you are not sure that cursor will be in initial state, call
+	 * ->moveToBeginning() after initialization.
+	 * @param Connection     $connection
+	 * @param ICursorDriver  $cursor CursorDriver in initial state (index=0)
+	 * @param CursorPosition $initialPosition
 	 */
-	public function __construct(ICursor $cursor)
+	public function __construct(Connection $connection, ICursorDriver $cursor, CursorPosition $initialPosition)
 	{
 		$this->cursor = $cursor;
+		$this->connection = $connection;
+		$this->position = $initialPosition;
 	}
 
 	/**
-	 * @return int
+	 * @return CursorPosition
 	 */
 	public function getPosition()
 	{
@@ -42,7 +47,7 @@ class TrackedCursor implements ICursor
 
 	public function getConnection() : Connection
 	{
-		return $this->cursor->getConnection();
+		return $this->connection;
 	}
 
 	public function getName() : string
@@ -50,191 +55,140 @@ class TrackedCursor implements ICursor
 		return $this->cursor->getName();
 	}
 
-	public function moveTo(int $index)
+	public function isOnRecord() : bool
 	{
-		if($index < 0) {
-			throw CursorException::untraceableValue("Given index is bellow zero. Use moveToEnd() + moveBy() combination.");
-		}
-		if($this->handleSpecialCases($index)) { // this was special case and was handled by special handling bellow
-			return;
-		}
-		$this->cursor->moveTo($index);
-		$this->position = $index;
+		return $this->cursor->isOnRecord();
 	}
 
-	public function moveBy(int $rows)
+	public function moveTo(int $index)
 	{
-		$originalPosition = $this->getPosition();
-		$index = $originalPosition + $rows;
-		//$this->moveTo($index); // todo: is this efficient?
+		$this->cursor->moveTo($index);
+		if ($this->cursor->isOnRecord()) {
+			$this->position->setPositionFromLeft(
+				$index
+			);
+			return;
+		}
 
-		if($this->handleSpecialCases($originalPosition + $rows)) { // this was special case and was handled by special handling bellow
+		if ($index === 0) {
+			$this->position->setPositionFromLeft(0); // BEGINNING
 			return;
 		}
-		try {
-			$this->cursor->moveBy($rows);
-			$this->position += $rows;
-		} catch (CursorException $e) {
-			$this->tryToRecoverFromError(
-				CursorException::cursorOverflow(),
-				$originalPosition
-			);
+		if ($index > 0 ) {
+			$this->position->setPositionFromRight(0); // END
+			return;
 		}
+		throw CursorException::cursorPosition_unknownError();
 	}
 
-	public function moveToBeginning()
+	public function moveFromEndTo(int $index)
 	{
-		$this->cursor->moveToBeginning();
-		$this->position = 0;
-	}
-
-	public function moveToFirst()
-	{
-		$this->cursor->moveToFirst();
-		$this->position = 1;
-	}
-
-	public function moveToLast()
-	{
-		$this->moveToEnd();
-		$this->moveBy(-1);
-	}
-
-	public function moveToEnd()
-	{
-		$rowsSkipped = $this->getConnection()->query(
-			"MOVE FORWARD ALL IN %n",
-			$this->getName()
-		);
-		if ($rowsSkipped === 0) {
-			// edge case: when already in END or LAST position
-			if ($this->total !== NULL)
-			{
-				$indexOfEnd = $this->total + 1;
-				$indexOfLast = $this->total;
-				if (
-					$this->position === $indexOfLast ||
-					$this->position === $indexOfEnd
-				)
-				{
-					$this->position = $indexOfEnd;
-					return;
-				}
-
-			} else {
-				throw CursorException::cannotMoveToTheEnd();
-			}
+		$this->cursor->moveFromEndTo($index);
+		if ($this->cursor->isOnRecord()) {
+			$this->position->setPositionFromRight(
+				$index
+			);
+			return;
 		}
-		$this->position += $rowsSkipped + 1;
-		$this->total = $this->position - 1; // end is one step after last item
-	}
 
-	public function fetch(int $rows)
-	{
-		$forward = $rows > 0;
-		$rows = $this->cursor->fetch($rows);
-		$this->position += count($rows) * ($forward ? 1 : -1);
-		return $rows;
+		if ($index === 0) {
+			$this->position->setPositionFromRight(0); // END
+			return;
+		}
+		if ($index > 0) {
+			$this->position->setPositionFromLeft(0); // BEGINNING
+			return;
+		}
+		throw CursorException::cursorPosition_unknownError();
 	}
 
-	public function fetchNext()
+	public function moveBy(int $rows)
 	{
-		$row = $this->cursor->fetchNext();
-		if($row !== FALSE) {
-			$this->position += 1;
+		// todo: use MOVE FORWARD n IN ... which returns number of rows read
+		$this->cursor->moveBy($rows);
+		if ($this->cursor->isOnRecord()) {
+			$this->position->movePositionBy($rows);
+			return;
+		}
+		if ($rows < 0) {
+			$this->position->setPositionFromLeft(0); // BEGINNING
+			return;
 		}
-		return $row;
+		if ($rows > 0) {
+			$this->position->setPositionFromRight(0); // END
+			return;
+		}
+		if ($rows === 0) {
+			return; // already on one of ends
+		}
+		throw CursorException::cursorPosition_unknownError();
 	}
 
-	public function fetchCurrent()
+	public function fetchRange(int $rows): array
 	{
-		return $this->cursor->fetchCurrent();
-	}
+		// todo: fixme overriding parameter
+		$result = $this->cursor->fetchRange($rows);
 
-	public function fetchNextSingle()
-	{
-		$value = $this->cursor->fetchNextSingle();
-		$this->position += 1;
-		return $value;
-	}
+		if($rows !== 0) { // zero does not move cursor
+			$forward = $rows > 0;
+			$this->position->movePositionBy(
+				count($result) * ($forward ? 1 : -1)
+			);
+		}
 
-	public function fetchCurrentSingle()
-	{
-		return $this->cursor->fetchCurrentSingle();
+		return $result;
 	}
 
-	public function fetchOneAt(int $index)
+	public function fetchOneAt(int $index) : array
 	{
-		$originalPosition = $this->getPosition();
 		$row = $this->cursor->fetchOneAt($index);
-		if($row !== FALSE) {
-			$this->position = $index;
-		} else {
-			$this->tryToRecoverFromError(
-				CursorException::noDataCursorHasBeenSetToZero(),
-				$originalPosition
-			);
+		if ($row !== NULL) {
+			if($index < 0) {
+				$this->position->setPositionFromRight(abs($index));
+			} else /* >= 0 */ {
+				$this->position->setPositionFromLeft($index);
+			}
+			return $row;
 		}
-		return $row;
-	}
 
-	public function fetchOneBy(int $rows)
-	{
-		$originalPosition = $this->getPosition();
-		$row = $this->cursor->fetchOneBy($rows);
-		if($row !== FALSE) {
-			$this->position += $rows;
-		} else {
-			$this->tryToRecoverFromError(
-				CursorException::noDataCursorHasBeenSetToZero(),
-				$originalPosition
-			);
+		if ($index === 0) {
+			$this->position->setPositionFromLeft(0); // BEGINNING
+			return NULL;
 		}
-		return $row;
-	}
 
-	public function fetchRemaining()
-	{
-		$data = $this->cursor->fetchRemaining();
-		$this->position += count($data);
-		return $data;
-	}
+		if ($index < 0) { // went from right to left -> hit beginning
+			$this->position->setPositionFromLeft(0);
+			return NULL;
+		}
+		if ($index > 0) { // went left->right -> hit end
+			$this->position->setPositionFromRight(0);
+			return NULL;
+		}
 
-	public function fetchForegoing()
-	{
-		$data = $this->cursor->fetchForegoing();
-		$this->position -= count($data);
-		return $data;
+		throw CursorException::cursorPosition_unknownError();
 	}
 
-	private function handleSpecialCases($index) : bool
+	public function fetchOneBy(int $rows): array
 	{
-		// if requested position was BEGINNING
-		if($index === 0) {
-			$this->moveToBeginning();
-			return TRUE;
+		$row = $this->cursor->fetchOneBy($rows);
+		if ($row !== NULL) {
+			$this->position->movePositionBy($rows);
+			return $row;
 		}
 
-		// if requested position was the END
-		if($this->total !== NULL) {
-			if($this->total + 1 === $index) {
-				$this->moveToEnd();
-				return TRUE;
-			}
+		if ($rows < 0) {
+			$this->position->setPositionFromLeft(0); // BEGINNING
+			return NULL;
+		}
+		if ($rows > 0) {
+			$this->position->setPositionFromRight(0); // END
+			return NULL;
+		}
+		if ($rows === 0) {
+			return NULL; // already on one of ends
 		}
 
-		// wasn't a special case
-		return FALSE;
+		throw CursorException::cursorPosition_unknownError();
 	}
 
-	private function tryToRecoverFromError(\Throwable $throwable, $moveTo)
-	{
-		try {
-			$this->moveTo($moveTo);
-		} catch (\Throwable $e) {
-			throw CursorException::cannotRecoverCursorIntoOriginalPosition($throwable);
-		}
-		
-		throw $throwable;
-	}
 }
\ No newline at end of file
diff --git a/tests/Store/PostgresDriver/TrackedCursorTest.phpt b/tests/Store/PostgresDriver/TrackedCursorTest.phpt
index 450538418d135c0ec19c4bbb65ce5fb75f404fee..2d05d39ee484cc8bbc9f03a7cefcc929f292deb1 100644
--- a/tests/Store/PostgresDriver/TrackedCursorTest.phpt
+++ b/tests/Store/PostgresDriver/TrackedCursorTest.phpt
@@ -1,20 +1,27 @@
-<?php
+<?php declare(strict_types = 1);
 /**
  * @testCase
  */
 
+// TODO: Add boundary checks!
+// TODO: moveBy() ->isOnRecord() ->moveBy() ->isOnRecord() (now I see state before and after command)
+// TODO: Cursor should have dedicated method for overriding data processing
+// TODO: Maybe construct Cursor class in TrackedCursor constructor?
+// TODO: Consistency check of inner cursor (headOnRecord === FALSE)
+
 namespace Grifart\Mappi\Tests\Store\Store\PostgresDriver;
 
-use Grifart\Mappi\Store\PostgresDriver\CursorException;
-use Grifart\Mappi\Store\PostgresDriver\Cursor;
-use Grifart\Mappi\Store\PostgresDriver\CursorFactory;
+use Grifart\Mappi\Store\PostgresDriver\CursorDriverFactory;
+use Grifart\Mappi\Store\PostgresDriver\CursorPosition;
+use Grifart\Mappi\Store\PostgresDriver\ICursorDriver;
 use Grifart\Mappi\Store\PostgresDriver\TrackedCursor;
+use Grifart\Mappi\Tests\Store\BaseTest;
 use Tester\Assert;
 
 require_once __DIR__ . "/../../bootstrap.php";
 require_once __DIR__ . "/CursorInterfaceTest.php";
 
-class TrackedCursorTest extends CursorInterfaceTest
+class TrackedCursorTest extends BaseTest
 {
 	/** @link https://en.wikipedia.org/wiki/42_(number)#Hitchhiker.27s_Guide_to_the_Galaxy */
 	const THE_MAGIC_NUMBER = 42;
@@ -27,9 +34,11 @@ class TrackedCursorTest extends CursorInterfaceTest
 		global $connection, $SQL_thousandRowsAscending;
 		$connection->begin();
 
-		$factory = new CursorFactory($connection);
+		$factory = new CursorDriverFactory($connection);
 		$this->uut = new TrackedCursor(
-			$factory->create($SQL_thousandRowsAscending, true)
+			$connection,
+			$factory->create($SQL_thousandRowsAscending, TRUE),
+			CursorPosition::fromLeft(0)
 		);
 		parent::setUp();
 	}
@@ -42,121 +51,412 @@ class TrackedCursorTest extends CursorInterfaceTest
 		parent::tearDown();
 	}
 
-	public function test_givenBeginningPosition_whenMoveLeft_thenGetError()
+	private function helper_fetchNextSingle()
 	{
-		$this->uut->moveToBeginning();
+		$rows = $this->uut->fetchRange(1);
+		if (count($rows) === 0) {
+			return NULL;
+		}
+		return $rows[0]["n"];
+	}
 
-		Assert::exception(function() {
-			// todo: what to do with this non-uniformity with original Cursor?
-			$this->uut->moveBy(-1);
-		}, CursorException::class, CursorException::MESSAGE_OVERFLOW);
+	private function helper_fetchCurrentSingle()
+	{
+		$rows = $this->uut->fetchRange(0);
+		if (count($rows) === 0) {
+			return NULL;
+		}
+		return $rows[0]["n"];
+	}
 
-		$this->uut->moveBy(1);
-		Assert::equal(1, $this->uut->fetchCurrentSingle());
+	private function helper_fetchPrevSingle()
+	{
+		$rows = $this->uut->fetchRange(-1);
+		if (count($rows) === 0) {
+			return NULL;
+		}
+		return $rows[0]["n"];
+	}
+
+	// initial position
+	public function test_givenCursor_whenGetCurrentRow_thenGetNothing()
+	{
+		Assert::count(0, $this->uut->fetchRange(0)); // must fail because initial position is 0
+		Assert::count(0, $this->uut->fetchRange(-1));
+		Assert::count(1, $this->uut->fetchRange(1));
 	}
 
-	public function test_givenEndPosition_whenMoveToRight_thenGetError()
+	public function test_givenInitialPosition_whenFetch_thenGetFirstRow()
 	{
-		$this->uut->moveToEnd();
+		Assert::same(1, $this->helper_fetchNextSingle());
+	}
 
-		Assert::exception(function() {
-			// todo: what to do with this non-uniformity with original Cursor?
-			$this->uut->moveBy(1);
-		}, CursorException::class, CursorException::MESSAGE_OVERFLOW);
+	// fetch( x == 0 )
+	public function test_givenSecondPosition_whenFetch0_thenGetCurrentRow()
+	{
+		// head:           .
+		// index: 0  1  2  3  4
+		// value:    1  2  3  4
+		$this->uut->moveTo(3);
+		Assert::same(3, $this->helper_fetchCurrentSingle());
 
-		$this->uut->moveBy(-1);
-		Assert::equal(1000, $this->uut->fetchCurrentSingle());
+		$result = $this->uut->fetchRange(0); // rows backwards + read current
+
+		Assert::count(1, $result);
+		Assert::same(3, current($result[0]));
 	}
 
-	// corrected behaviour of original cursor
-	public function test_givenFirstPosition_whenMoveToLeft_thenImOnFirstValue()
+	// fetch( x > 0 )
+	public function test_givenSecondPosition_whenFetchForward_thenGetRowsAfterCursor()
 	{
-		$this->uut->moveToFirst();
+		// head:           .
+		// index: 0  1  2  3  4
+		// value:    1  2  3  4
+		$this->uut->moveTo(3);
+		Assert::same(3, $this->helper_fetchCurrentSingle());
+
+		// head:                 .
+		// index: 0  1  2  3  4  5  6
+		// value:    1  2  3  4  5  6
+		$result = $this->uut->fetchRange(2); // rows backwards + read current
+		// todo: split into fetch decorator for those special fetch* methods
+
+		Assert::count(2, $result);
+		Assert::same(4, current($result[0]));
+		Assert::same(5, current($result[1]));
+		Assert::same(5, $this->helper_fetchCurrentSingle());
+	}
 
+	// fetch( x < 0 )
+	public function test_givenSecondPosition_whenFetchBackwards_thenGetRowsBeforeCursor()
+	{
+		// head:           .
+		// index: 0  1  2  3  4
+		// value:    1  2  3  4
+		$this->uut->moveTo(3);
+		Assert::same(3, $this->helper_fetchCurrentSingle());
+
+		// head:     .
+		// index: 0  1  2  3  4
+		// value:    1  2  3  4
+		$result = $this->uut->fetchRange(-2); // rows backwards + read current
+		// todo: split into fetch decorator for those special fetch* methods
+
+		Assert::count(2, $result);
+		Assert::same(2, current($result[0]));
+		Assert::same(1, current($result[1]));
+		Assert::same(1, $this->helper_fetchCurrentSingle());
+	}
+
+	// moveToBeginning()
+	public function test_givenLastPosition_whenMoveToTheBeginning_thenFetchingNextRowWillBeFirstRow()
+	{
+		// Arrange
+		$this->uut->moveFromEndTo(1);
+
+		// Act
+		$this->uut->moveTo(0);
+
+		//Assert
+		Assert::same(1, $this->helper_fetchNextSingle());
+	}
+
+	// moveToFirst()
+	public function test_givenLastPosition_whenMoveToTheFirst_thenFetchingCurrentWillBeFirstRow()
+	{
+		// Arrange
+		$this->uut->moveFromEndTo(1);
+
+		// Act
+		$this->uut->moveTo(1);
+
+		//Assert
+		Assert::same(1, $this->helper_fetchCurrentSingle());
+	}
+
+	// moveToEnd()
+	public function test_givenInitialPosition_whenMoveToEnd_thenFetchingOneBacwardsWillBeTheLastRow()
+	{
+		$this->uut->moveFromEndTo(0);
+
+		Assert::null($this->helper_fetchCurrentSingle());
+
+		$data = $this->helper_fetchPrevSingle();
+		Assert::same(1000, $data);
+	}
+
+	// moveToLast()
+	public function test_givenInitialPosition_whenMoveToLast_thenFetchingCurrentWillBeLastRow()
+	{
+		$this->uut->moveFromEndTo(1);
+		Assert::same(1000, $this->helper_fetchCurrentSingle());
+
+		Assert::null($this->helper_fetchNextSingle());
+	}
+
+	// moveBy()
+	public function test_givenInitialPosition_whenMoveBy2_thenGetSecondValue()
+	{
+		$this->uut->moveBy(2);
+		Assert::same(2, $this->helper_fetchCurrentSingle());
+	}
+
+	public function test_givenSecondPosition_whenMoveOneBack_thenGetFirstValue()
+	{
+		$this->uut->moveTo(2);
 		$this->uut->moveBy(-1);
-		// todo: what to do with this non-uniformity with original Cursor?
-		Assert::equal(1, $this->uut->fetchNextSingle());
+		Assert::same(1, $this->helper_fetchCurrentSingle());
+		$this->uut->moveBy(-1);
+		Assert::null($this->helper_fetchCurrentSingle());
 	}
 
-	public function test_givenLastPosition_whenMoveToRight_thenGetError()
+	// fetchOneAt()
+	public function test_givenInitialPosition_whenFetch5_thenGetFive()
 	{
-		// todo: what to do with this non-uniformity with original Cursor?
-		$this->uut->moveToLast();
-		$this->uut->moveBy(1); // here is ok, now in original cursor
+		$data = $this->uut->fetchOneAt(5);
+		Assert::same(5, $data["n"]);
+	}
 
-		$this->uut->moveBy(-1); // todo: make decorator which will make this nicer
-		Assert::equal(1000, $this->uut->fetchCurrentSingle());
+	public function test_givenNonInitialPosition_whenFetch5_thenGetFive()
+	{
+		$this->uut->moveTo(234); // wherever
+
+		$data = $this->uut->fetchOneAt(5);
+		Assert::same(5, $data["n"]);
 	}
 
+	// fetchRemaining()
+	public function test_given5BeforeEndPosition_whenFetchRemaining_thenGetLastFile()
+	{
+		$this->uut->moveTo(995);
 
+		$result = $this->uut->fetchRange(ICursorDriver::FETCH_REMAINING);
 
-	// ->getPosition() tests:
+		Assert::count(5, $result);
+		Assert::same(996, current($result[0]));
+		Assert::same(997, current($result[1]));
+		Assert::same(998, current($result[2]));
+		Assert::same(999, current($result[3]));
+		Assert::same(1000, current($result[4]));
 
-	// moveToBeginning()
-	public function test_givenInitialPosition_whenGetPosition_thenGetZero()
+		Assert::null($this->helper_fetchCurrentSingle());
+		Assert::equal(1000, $this->helper_fetchPrevSingle());
+	}
+
+	// fetchRemaining()
+	public function test_given5AfterStart_whenFetchForegoing_thenGetFirstFive()
 	{
-		Assert::equal(0, $this->uut->getPosition());
+		$this->uut->moveTo(6);
 
-		$this->uut->moveToBeginning();
-		Assert::equal(0, $this->uut->getPosition());
+		$result = $this->uut->fetchRange(ICursorDriver::FETCH_FOREGOING);
 
-		$this->uut->moveToFirst();
-		Assert::equal(1, $this->uut->getPosition());
+		Assert::count(5, $result);
+		Assert::same(5, current($result[0]));
+		Assert::same(4, current($result[1]));
+		Assert::same(3, current($result[2]));
+		Assert::same(2, current($result[3]));
+		Assert::same(1, current($result[4]));
+
+		Assert::null($this->helper_fetchCurrentSingle());
 	}
 
-	public function test_givenMiddlePosition_whenMoveAround_thenGetCorrectPosition()
+	// edge cases:
+	public function test_givenInitialPosition_whenMoveToLeft_thenGetError()
 	{
-		$this->uut->moveTo(self::THE_MAGIC_NUMBER);
-		$this->uut->moveTo(self::THE_MAGIC_NUMBER + self::THE_MAGIC_NUMBER);
-		$this->uut->moveBy(-self::THE_MAGIC_NUMBER);
-		$this->uut->moveBy(+self::THE_MAGIC_NUMBER);
-		$this->uut->moveBy(-self::THE_MAGIC_NUMBER);
-		Assert::equal(self::THE_MAGIC_NUMBER, $this->uut->getPosition());
+		Assert::false($this->uut->isOnRecord()); // initial position
+
+		// start: index === 0
+		$this->uut->moveBy(-1);
+		Assert::false($this->uut->isOnRecord());
+
+		// index === 0
+		$data = $this->uut->fetchRange(-1);
+		Assert::count(0, $data);
+
+		$firstValue = $this->helper_fetchNextSingle();
+		Assert::equal(1, $firstValue);
+		// index === 1
 	}
 
-	public function test_givenEndPosition_whenGetPosition_thenGetTotalPlusOne()
+	public function test_givenEndPosition_whenMoveToRight_thenStayInPlace()
 	{
-		$this->uut->moveToEnd();
-		Assert::equal(1001, $this->uut->getPosition());
+		$this->uut->moveFromEndTo(0);
+
+		Assert::null($this->helper_fetchNextSingle());
+		Assert::null($this->helper_fetchCurrentSingle());
+		Assert::equal(1000, $this->helper_fetchPrevSingle());
 	}
 
-	public function test_givenInitialPosition_whenMoveToLast_thenGetTotal()
+	public function test_giveSomePosition_whenMoveToAfterEnd_thenWillBeAtTheEnd()
 	{
-		$this->uut->moveToLast();
-		Assert::equal(1000, $this->uut->getPosition());
+		$this->uut->moveTo(25); // some position
+
+		$this->uut->moveTo(9999);
+
+		Assert::null($this->helper_fetchCurrentSingle());
+		Assert::equal(1000, $this->helper_fetchPrevSingle());
 	}
 
-	// nasty edge cases:
-	public function test_givenLastPosition_whenMoveToEnd_thenGetEndPosition()
+	public function test_givenFirstPosition_whenMoveToLeft_thenWillBeAtBeginning()
 	{
-		$this->uut->moveToLast();
-		$this->uut->moveToEnd();
-		Assert::equal(1001, $this->uut->getPosition());
+		// Arrange
+		Assert::false($this->uut->isOnRecord());
+		$this->uut->moveTo(1);
+		Assert::true($this->uut->isOnRecord());
+
+		// Act
+		$this->uut->moveBy(-1);
+
+		// Assert
+		Assert::false($this->uut->isOnRecord());
+		Assert::null($this->helper_fetchCurrentSingle());
+		Assert::null($this->helper_fetchPrevSingle());
 
-		// regression test:
-		$this->uut->moveToEnd();
-		Assert::equal(1001, $this->uut->getPosition());
+		Assert::equal(1, $this->helper_fetchNextSingle());
+		Assert::true($this->uut->isOnRecord());
 	}
 
-	public function test_givenEndPosition_whenMoveToLast_thenGetLastPosition()
+	public function test_givenLastPosition_whenMoveToRight_thenReachTheEnd()
 	{
-		$this->uut->moveToEnd();
-		$this->uut->moveToLast();
-		Assert::equal(1000, $this->uut->getPosition());
+		$this->uut->moveFromEndTo(1);
+		Assert::true($this->uut->isOnRecord());
 
-		// regression test:
-		$this->uut->moveToLast();
-		Assert::equal(1000, $this->uut->getPosition());
+		$this->uut->moveBy(1);
+
+		Assert::false($this->uut->isOnRecord());
+		Assert::null($this->helper_fetchCurrentSingle());
+		Assert::null($this->helper_fetchNextSingle());
+
+		Assert::equal(1000, $this->helper_fetchPrevSingle());
+		Assert::true($this->uut->isOnRecord());
 	}
 
-	public function test_giveSomePosition_whenMoveToBeginning_thenGetToBeginning()
+	public function test_giveSomePosition_whenMoveToBeginning_thenWillBeAtBeginning()
 	{
 		$this->uut->moveTo(42);
 		$this->uut->moveTo(0);
-
-		Assert::equal(0, $this->uut->getPosition());
-		Assert::equal(1, $this->uut->fetchNextSingle());
+		Assert::equal(1, $this->helper_fetchNextSingle());
 	}
+
+
+
+
+//
+//	public function test_givenBeginningPosition_whenMoveLeft_thenGetError()
+//	{
+//		$this->uut->moveToBeginning();
+//
+//		Assert::exception(function() {
+//			// todo: what to do with this non-uniformity with original CursorDriver?
+//			$this->uut->moveBy(-1);
+//		}, CursorException::class, CursorException::MESSAGE_OVERFLOW);
+//
+//		$this->uut->moveBy(1);
+//		Assert::equal(1, $this->uut->fetchCurrentSingle());
+//	}
+//
+//	public function test_givenEndPosition_whenMoveToRight_thenGetError()
+//	{
+//		$this->uut->moveToEnd();
+//
+//		Assert::exception(function() {
+//			// todo: what to do with this non-uniformity with original CursorDriver?
+//			$this->uut->moveBy(1);
+//		}, CursorException::class, CursorException::MESSAGE_OVERFLOW);
+//
+//		$this->uut->moveBy(-1);
+//		Assert::equal(1000, $this->uut->fetchCurrentSingle());
+//	}
+//
+//	// corrected behaviour of original cursor
+//	public function test_givenFirstPosition_whenMoveToLeft_thenImOnFirstValue()
+//	{
+//		$this->uut->moveToFirst();
+//
+//		$this->uut->moveBy(-1);
+//		// todo: what to do with this non-uniformity with original CursorDriver?
+//		Assert::equal(1, $this->uut->fetchNextSingle());
+//	}
+//
+//	public function test_givenLastPosition_whenMoveToRight_thenGetError()
+//	{
+//		// todo: what to do with this non-uniformity with original CursorDriver?
+//		$this->uut->moveToLast();
+//		$this->uut->moveBy(1); // here is ok, now in original cursor
+//
+//		$this->uut->moveBy(-1); // todo: make decorator which will make this nicer
+//		Assert::equal(1000, $this->uut->fetchCurrentSingle());
+//	}
+//
+//
+//
+//	// ->getPosition() tests:
+//
+//	// moveToBeginning()
+//	public function test_givenInitialPosition_whenGetPosition_thenGetZero()
+//	{
+//		Assert::equal(0, $this->uut->getPosition());
+//
+//		$this->uut->moveToBeginning();
+//		Assert::equal(0, $this->uut->getPosition());
+//
+//		$this->uut->moveToFirst();
+//		Assert::equal(1, $this->uut->getPosition());
+//	}
+//
+//	public function test_givenMiddlePosition_whenMoveAround_thenGetCorrectPosition()
+//	{
+//		$this->uut->moveTo(self::THE_MAGIC_NUMBER);
+//		$this->uut->moveTo(self::THE_MAGIC_NUMBER + self::THE_MAGIC_NUMBER);
+//		$this->uut->moveBy(-self::THE_MAGIC_NUMBER);
+//		$this->uut->moveBy(+self::THE_MAGIC_NUMBER);
+//		$this->uut->moveBy(-self::THE_MAGIC_NUMBER);
+//		Assert::equal(self::THE_MAGIC_NUMBER, $this->uut->getPosition());
+//	}
+//
+//	public function test_givenEndPosition_whenGetPosition_thenGetTotalPlusOne()
+//	{
+//		$this->uut->moveToEnd();
+//		Assert::equal(1001, $this->uut->getPosition());
+//	}
+//
+//	public function test_givenInitialPosition_whenMoveToLast_thenGetTotal()
+//	{
+//		$this->uut->moveToLast();
+//		Assert::equal(1000, $this->uut->getPosition());
+//	}
+//
+//	// nasty edge cases:
+//	public function test_givenLastPosition_whenMoveToEnd_thenGetEndPosition()
+//	{
+//		$this->uut->moveToLast();
+//		$this->uut->moveToEnd();
+//		Assert::equal(1001, $this->uut->getPosition());
+//
+//		// regression test:
+//		$this->uut->moveToEnd();
+//		Assert::equal(1001, $this->uut->getPosition());
+//	}
+//
+//	public function test_givenEndPosition_whenMoveToLast_thenGetLastPosition()
+//	{
+//		$this->uut->moveToEnd();
+//		$this->uut->moveToLast();
+//		Assert::equal(1000, $this->uut->getPosition());
+//
+//		// regression test:
+//		$this->uut->moveToLast();
+//		Assert::equal(1000, $this->uut->getPosition());
+//	}
+//
+//	public function test_giveSomePosition_whenMoveToBeginning_thenGetToBeginning()
+//	{
+//		$this->uut->moveTo(42);
+//		$this->uut->moveTo(0);
+//
+//		Assert::equal(0, $this->uut->getPosition());
+//		Assert::equal(1, $this->uut->fetchNextSingle());
+//	}
 }
 
 (new TrackedCursorTest())->run();
\ No newline at end of file