[vfs][file] Implement seek and add tests.

Also fixed Close().

TEST=fx run-test vfs_cpp_tests

Change-Id: Ice636c0f1e79d29c2f80f6547cb9c82f1ca0deb2
diff --git a/public/lib/vfs/cpp/connection.cc b/public/lib/vfs/cpp/connection.cc
index 995d9b7..341b0d5 100644
--- a/public/lib/vfs/cpp/connection.cc
+++ b/public/lib/vfs/cpp/connection.cc
@@ -20,7 +20,8 @@
 }
 
 void Connection::Close(Node* vn, fuchsia::io::Node::CloseCallback callback) {
-  callback(vn->Close(this));
+  callback(ZX_OK);
+  vn->Close(this);
   // |this| is destroyed at this point.
 }
 
diff --git a/public/lib/vfs/cpp/file.cc b/public/lib/vfs/cpp/file.cc
index b06319c..1d56a67 100644
--- a/public/lib/vfs/cpp/file.cc
+++ b/public/lib/vfs/cpp/file.cc
@@ -39,6 +39,8 @@
   return ZX_OK;
 }
 
+size_t File::GetCapacity() { return std::numeric_limits<size_t>::max(); }
+
 bool File::IsDirectory() const { return false; }
 
 }  // namespace vfs
diff --git a/public/lib/vfs/cpp/file.h b/public/lib/vfs/cpp/file.h
index 5ac694c..7e4d5df 100644
--- a/public/lib/vfs/cpp/file.h
+++ b/public/lib/vfs/cpp/file.h
@@ -50,6 +50,17 @@
   // Override that describes this object as a file.
   void Describe(fuchsia::io::NodeInfo* out_info) override;
 
+  // Returns current file length.
+  //
+  // All implementations should implement this.
+  virtual uint64_t GetLength() = 0;
+
+  // Returns file capacity.
+  //
+  // Seek() uses this to return ZX_ERR_OUT_OF_RANGE if new seek is more than
+  // this value.
+  virtual size_t GetCapacity();
+
  protected:
   zx_status_t CreateConnection(
       uint32_t flags, std::unique_ptr<Connection>* connection) override;
diff --git a/public/lib/vfs/cpp/file_unittest.cc b/public/lib/vfs/cpp/file_unittest.cc
index aa498a7..7a26c5a 100644
--- a/public/lib/vfs/cpp/file_unittest.cc
+++ b/public/lib/vfs/cpp/file_unittest.cc
@@ -46,6 +46,10 @@
     return ZX_OK;
   }
 
+  uint64_t GetLength() override { return buffer_->size(); }
+
+  size_t GetCapacity() override { return buffer_->size(); }
+
  private:
   std::vector<uint8_t>* buffer_;
 };
diff --git a/public/lib/vfs/cpp/internal/file_connection.cc b/public/lib/vfs/cpp/internal/file_connection.cc
index c0b79de..1fbfa50 100644
--- a/public/lib/vfs/cpp/internal/file_connection.cc
+++ b/public/lib/vfs/cpp/internal/file_connection.cc
@@ -109,11 +109,32 @@
   callback(status, actual);
 }
 
-void FileConnection::Seek(int64_t offset, fuchsia::io::SeekOrigin start,
+void FileConnection::Seek(int64_t new_offset, fuchsia::io::SeekOrigin seek,
                           SeekCallback callback) {
-  // TODO: Check flags.
-  // TODO: Implement seek.
-  callback(ZX_OK, 0u);
+  int64_t cur_len = vn_->GetLength();
+  size_t capacity = vn_->GetCapacity();
+  uint64_t calculated_offset = 0u;
+  switch (seek) {
+    case fuchsia::io::SeekOrigin::START:
+      calculated_offset = new_offset;
+      break;
+    case fuchsia::io::SeekOrigin::CURRENT:
+      calculated_offset = offset() + new_offset;
+      break;
+    case fuchsia::io::SeekOrigin::END:
+      calculated_offset = cur_len + new_offset;
+      break;
+    default:
+      callback(ZX_ERR_INVALID_ARGS, 0u);
+      return;
+  }
+
+  if (static_cast<size_t>(calculated_offset) > capacity) {
+    callback(ZX_ERR_OUT_OF_RANGE, offset());
+    return;
+  }
+  set_offset(calculated_offset);
+  callback(ZX_OK, offset());
 }
 
 void FileConnection::Truncate(uint64_t length, TruncateCallback callback) {
diff --git a/public/lib/vfs/cpp/node.cc b/public/lib/vfs/cpp/node.cc
index 35593c7..88f5f51 100644
--- a/public/lib/vfs/cpp/node.cc
+++ b/public/lib/vfs/cpp/node.cc
@@ -24,9 +24,13 @@
 
 Node::~Node() = default;
 
-zx_status_t Node::Close(Connection* connection) {
-  RemoveConnection(connection);
-  return ZX_OK;
+std::unique_ptr<Connection> Node::Close(Connection* connection) {
+  auto connection_iterator = std::find_if(
+      connections_.begin(), connections_.end(),
+      [connection](const auto& entry) { return entry.get() == connection; });
+  auto ret = std::move(*connection_iterator);
+  connections_.erase(connection_iterator);
+  return ret;
 }
 
 zx_status_t Node::Sync() { return ZX_ERR_NOT_SUPPORTED; }
@@ -104,12 +108,6 @@
   request.write(0, &msg, sizeof(msg), nullptr, 0);
 }
 
-void Node::RemoveConnection(Connection* connection) {
-  connections_.erase(std::find_if(
-      connections_.begin(), connections_.end(),
-      [connection](const auto& entry) { return entry.get() == connection; }));
-}
-
 void Node::AddConnection(std::unique_ptr<Connection> connection) {
   connections_.push_back(std::move(connection));
 }
diff --git a/public/lib/vfs/cpp/node.h b/public/lib/vfs/cpp/node.h
index 8839a4f..8bd8649 100644
--- a/public/lib/vfs/cpp/node.h
+++ b/public/lib/vfs/cpp/node.h
@@ -31,9 +31,9 @@
   Node(const Node&) = delete;
   Node& operator=(const Node&) = delete;
 
-  // Notifies |Node| that it should remove |connection| from its list as it is
-  // getting closed.
-  virtual zx_status_t Close(Connection* connection);
+  // Notifies |Node| that it should remove and return
+  // |connection| from its list as it is getting closed.
+  virtual std::unique_ptr<Connection> Close(Connection* connection);
 
   // Implementation of |fuchsia.io.Node/Describe|.
   //
@@ -87,11 +87,6 @@
   void SendOnOpenEventOnError(uint32_t flags, zx::channel request,
                               zx_status_t status);
 
-  // Destroys the given connection.
-  //
-  // The underlying channel is closed.
-  void RemoveConnection(Connection* connection);
-
   // Store given connection.
   void AddConnection(std::unique_ptr<Connection> connection);
 
diff --git a/public/lib/vfs/cpp/pseudo_file.cc b/public/lib/vfs/cpp/pseudo_file.cc
index 2ca2981..e3c80b1 100644
--- a/public/lib/vfs/cpp/pseudo_file.cc
+++ b/public/lib/vfs/cpp/pseudo_file.cc
@@ -50,6 +50,20 @@
   return fuchsia::io::OPEN_FLAG_APPEND;
 }
 
+uint64_t BufferedPseudoFile::GetLength() {
+  // this should never be called
+  ZX_DEBUG_ASSERT(false);
+
+  return 0u;
+}
+
+size_t BufferedPseudoFile::GetCapacity() {
+  // this should never be called
+  ZX_DEBUG_ASSERT(false);
+
+  return buffer_capacity_;
+}
+
 BufferedPseudoFile::Content::Content(BufferedPseudoFile* file, uint32_t flags,
                                      std::vector<uint8_t> content)
     : Connection(flags),
@@ -119,6 +133,12 @@
   return ZX_OK;
 }
 
+uint64_t BufferedPseudoFile::Content::GetLength() { return buffer_.size(); }
+
+size_t BufferedPseudoFile::Content::GetCapacity() {
+  return file_->buffer_capacity_;
+}
+
 void BufferedPseudoFile::Content::SetInputLength(size_t length) {
   ZX_DEBUG_ASSERT(length <= file_->buffer_capacity_);
 
@@ -141,8 +161,9 @@
   return status;
 }
 
-zx_status_t BufferedPseudoFile::Content::Close(Connection* connection) {
-  Node::Close(connection);
+std::unique_ptr<Connection> BufferedPseudoFile::Content::Close(
+    Connection* connection) {
+  File::Close(connection);
   return file_->Close(this);
 }
 
diff --git a/public/lib/vfs/cpp/pseudo_file.h b/public/lib/vfs/cpp/pseudo_file.h
index 1c14133..875c761 100644
--- a/public/lib/vfs/cpp/pseudo_file.h
+++ b/public/lib/vfs/cpp/pseudo_file.h
@@ -67,6 +67,10 @@
                         uint64_t* out_actual) override;
     zx_status_t Truncate(uint64_t length) override;
 
+    uint64_t GetLength() override;
+
+    size_t GetCapacity() override;
+
     // Connection implementation:
     zx_status_t Bind(zx::channel request,
                      async_dispatcher_t* dispatcher) override;
@@ -74,7 +78,7 @@
     void SendOnOpenEvent(zx_status_t status) override;
 
     // Node implementation
-    zx_status_t Close(Connection* connection) override;
+    std::unique_ptr<Connection> Close(Connection* connection) override;
 
    protected:
     uint32_t GetAdditionalAllowedFlags() const override;
@@ -93,6 +97,11 @@
     bool dirty_ = false;
   };
 
+  // |File| implementations:
+  uint64_t GetLength() override;
+
+  size_t GetCapacity() override;
+
   ReadHandler const read_handler_;
   WriteHandler const write_handler_;
   const size_t buffer_capacity_;
diff --git a/public/lib/vfs/cpp/pseudo_file_unittest.cc b/public/lib/vfs/cpp/pseudo_file_unittest.cc
index 3fed558..c90d5bc 100644
--- a/public/lib/vfs/cpp/pseudo_file_unittest.cc
+++ b/public/lib/vfs/cpp/pseudo_file_unittest.cc
@@ -25,17 +25,25 @@
   vfs::BufferedPseudoFile* file() { return file_.get(); };
 
   static FileWrapper CreateReadWriteFile(std::string initial_str,
-                                         size_t capacity) {
-    return FileWrapper(true, initial_str, capacity);
+                                         size_t capacity,
+                                         bool start_loop = true) {
+    return FileWrapper(true, initial_str, capacity, start_loop);
   }
 
-  static FileWrapper CreateReadOnlyFile(std::string initial_str) {
-    return FileWrapper(false, initial_str, initial_str.length());
+  static FileWrapper CreateReadOnlyFile(std::string initial_str,
+                                        bool start_loop = true) {
+    return FileWrapper(false, initial_str, initial_str.length(), start_loop);
   }
 
+  async_dispatcher_t* dispatcher() { return loop_.dispatcher(); }
+
+  async::Loop& loop() { return loop_; }
+
  private:
-  FileWrapper(bool write_allowed, std::string initial_str, size_t capacity)
-      : buffer_(std::move(initial_str)) {
+  FileWrapper(bool write_allowed, std::string initial_str, size_t capacity,
+              bool start_loop)
+      : buffer_(std::move(initial_str)),
+        loop_(&kAsyncLoopConfigNoAttachToThread) {
     auto readFn = [this](std::vector<uint8_t>* output) {
       output->resize(buffer_.length());
       std::copy(buffer_.begin(), buffer_.end(), output->begin());
@@ -53,10 +61,14 @@
 
     file_ = std::make_unique<vfs::BufferedPseudoFile>(
         std::move(readFn), std::move(writeFn), capacity);
+    if (start_loop) {
+      loop_.StartThread("vfs test thread");
+    }
   }
 
   std::unique_ptr<vfs::BufferedPseudoFile> file_;
   std::string buffer_;
+  async::Loop loop_;
 };
 
 class BufferedPseudoFileTest : public gtest::RealLoopFixture {
@@ -76,7 +88,7 @@
       bool on_open_called = false;
       node_ptr.events().OnOpen =
           [&](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> info) {
-            EXPECT_FALSE(on_open_called);  // should be caleld only once
+            EXPECT_FALSE(on_open_called);  // should be called only once
             on_open_called = true;
             EXPECT_EQ(expected_status, status);
             if (expected_status == ZX_OK) {
@@ -91,6 +103,107 @@
     }
   }
 
+  fuchsia::io::FileSyncPtr OpenReadWrite(vfs::Node* node,
+                                         async_dispatcher_t* dispatcher) {
+    return OpenFile(
+        node,
+        fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE,
+        dispatcher);
+  }
+
+  fuchsia::io::FileSyncPtr OpenRead(vfs::Node* node,
+                                    async_dispatcher_t* dispatcher) {
+    return OpenFile(node, fuchsia::io::OPEN_RIGHT_READABLE, dispatcher);
+  }
+
+  fuchsia::io::FileSyncPtr OpenFile(vfs::Node* node, uint32_t flags,
+                                    async_dispatcher_t* dispatcher) {
+    fuchsia::io::FileSyncPtr ptr;
+    node->Serve(flags, ptr.NewRequest().TakeChannel(), dispatcher);
+    return ptr;
+  }
+
+  void AssertWriteAt(fuchsia::io::FileSyncPtr& file, const std::string& str,
+                     int offset, zx_status_t expected_status = ZX_OK,
+                     int expected_actual = -1) {
+    zx_status_t status;
+    uint64_t actual;
+    std::vector<uint8_t> buffer;
+    buffer.resize(str.length());
+    std::copy(str.begin(), str.end(), buffer.begin());
+    file->WriteAt(buffer, offset, &status, &actual);
+    ASSERT_EQ(expected_status, status);
+    ASSERT_EQ(expected_actual == -1 ? str.length() : expected_actual, actual);
+  }
+
+  void AssertWrite(fuchsia::io::FileSyncPtr& file, const std::string& str,
+                   zx_status_t expected_status = ZX_OK,
+                   int expected_actual = -1) {
+    zx_status_t status;
+    uint64_t actual;
+    std::vector<uint8_t> buffer;
+    buffer.resize(str.length());
+    std::copy(str.begin(), str.end(), buffer.begin());
+    file->Write(buffer, &status, &actual);
+    ASSERT_EQ(expected_status, status);
+    ASSERT_EQ(expected_actual == -1 ? str.length() : expected_actual, actual);
+  }
+
+  void AssertReadAt(fuchsia::io::FileSyncPtr& file, int offset, int count,
+                    const std::string& expected_str,
+                    zx_status_t expected_status = ZX_OK) {
+    zx_status_t status;
+    std::vector<uint8_t> buffer;
+    file->ReadAt(count, offset, &status, &buffer);
+    ASSERT_EQ(expected_status, status);
+    std::string str(buffer.size(), 0);
+    std::copy(buffer.begin(), buffer.end(), str.begin());
+    ASSERT_EQ(expected_str, str);
+  }
+
+  void AssertRead(fuchsia::io::FileSyncPtr& file, int count,
+                  const std::string& expected_str,
+                  zx_status_t expected_status = ZX_OK) {
+    zx_status_t status;
+    std::vector<uint8_t> buffer;
+    file->Read(count, &status, &buffer);
+    ASSERT_EQ(expected_status, status);
+    std::string str(buffer.size(), 0);
+    std::copy(buffer.begin(), buffer.end(), str.begin());
+    ASSERT_EQ(expected_str, str);
+  }
+
+  void AssertTruncate(fuchsia::io::FileSyncPtr& file, int count,
+                      zx_status_t expected_status = ZX_OK) {
+    zx_status_t status;
+    file->Truncate(count, &status);
+    ASSERT_EQ(expected_status, status);
+  }
+
+  void AssertSeek(fuchsia::io::FileSyncPtr& file, int64_t offest,
+                  fuchsia::io::SeekOrigin seek, uint64_t expected_offset,
+                  zx_status_t expected_status = ZX_OK) {
+    zx_status_t status;
+    uint64_t new_offset;
+    file->Seek(offest, seek, &status, &new_offset);
+    ASSERT_EQ(expected_status, status);
+    ASSERT_EQ(expected_offset, new_offset);
+  }
+
+  void CloseFile(fuchsia::io::FileSyncPtr& file,
+                 zx_status_t expected_status = ZX_OK) {
+    zx_status_t status = 1;
+    file->Close(&status);
+    EXPECT_EQ(expected_status, status);
+  }
+
+  void AssertFileWrapperState(FileWrapper& file_wrapper,
+                              const std::string& expected_str) {
+    ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
+        [&]() { return file_wrapper.buffer() == expected_str; }, zx::sec(1)))
+        << file_wrapper.buffer();
+  }
+
   int OpenAsFD(vfs::Node* node, async_dispatcher_t* dispatcher) {
     zx::channel local, remote;
     EXPECT_EQ(ZX_OK, zx::channel::create(0, &local, &remote));
@@ -106,7 +219,7 @@
 };
 
 TEST_F(BufferedPseudoFileTest, ServeOnInValidFlagsForReadWriteFile) {
-  auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100);
+  auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100, false);
   {
     SCOPED_TRACE("OPEN_FLAG_DIRECTORY");
     AssertOpen(file_wrapper.file(), dispatcher(),
@@ -154,7 +267,7 @@
 }
 
 TEST_F(BufferedPseudoFileTest, ServeOnValidFlagsForReadWriteFile) {
-  auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100);
+  auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100, false);
   uint32_t allowed_flags[] = {
       fuchsia::io::OPEN_RIGHT_READABLE, fuchsia::io::OPEN_RIGHT_WRITABLE,
       fuchsia::io::OPEN_FLAG_NODE_REFERENCE, fuchsia::io::OPEN_FLAG_TRUNCATE};
@@ -165,7 +278,7 @@
 }
 
 TEST_F(BufferedPseudoFileTest, ServeOnValidFlagsForReadOnlyFile) {
-  auto file_wrapper = FileWrapper::CreateReadOnlyFile("test_str");
+  auto file_wrapper = FileWrapper::CreateReadOnlyFile("test_str", false);
   uint32_t allowed_flags[] = {fuchsia::io::OPEN_RIGHT_READABLE,
                               fuchsia::io::OPEN_FLAG_NODE_REFERENCE};
   for (auto allowed_flag : allowed_flags) {
@@ -176,10 +289,8 @@
 
 TEST_F(BufferedPseudoFileTest, Simple) {
   auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100);
-  async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
-  loop.StartThread("vfs test thread");
 
-  int fd = OpenAsFD(file_wrapper.file(), loop.dispatcher());
+  int fd = OpenAsFD(file_wrapper.file(), file_wrapper.dispatcher());
   ASSERT_LE(0, fd);
 
   char buffer[1024];
@@ -192,12 +303,244 @@
   EXPECT_STREQ("abcd_", buffer);
 
   ASSERT_GE(0, close(fd));
-  loop.RunUntilIdle();
+  file_wrapper.loop().RunUntilIdle();
 
-  ASSERT_TRUE(RunLoopWithTimeoutOrUntil(
-      [&file_wrapper]() { return file_wrapper.buffer() == "abcd_str"; },
-      zx::sec(1)))
-      << file_wrapper.buffer();
+  AssertFileWrapperState(file_wrapper, "abcd_str");
+}
+
+TEST_F(BufferedPseudoFileTest, WriteAt) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertWriteAt(file, "was", 5);
+
+  const std::string updated_str = "this wasa test string";
+  // confirm by reading
+  AssertRead(file, str.length(), updated_str);
+
+  // make sure file was not updated before conenction was closed.
+  ASSERT_EQ(file_wrapper.buffer(), str);
+
+  CloseFile(file);
+
+  AssertFileWrapperState(file_wrapper, updated_str);
+}
+
+TEST_F(BufferedPseudoFileTest, MultipleWriteAt) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertWriteAt(file, "was", 5);
+
+  AssertWriteAt(file, "tests", 10);
+
+  const std::string updated_str = "this wasa testsstring";
+  // confirm by reading
+  AssertRead(file, str.length(), updated_str);
+
+  // make sure file was not updated before conenction was closed.
+  ASSERT_EQ(file_wrapper.buffer(), str);
+
+  CloseFile(file);
+
+  AssertFileWrapperState(file_wrapper, updated_str);
+}
+
+TEST_F(BufferedPseudoFileTest, ReadAt) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertReadAt(file, 5, 10, str.substr(5, 10));
+
+  // try one more
+  AssertReadAt(file, 15, 5, str.substr(15, 5));
+}
+
+TEST_F(BufferedPseudoFileTest, Read) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertRead(file, 10, str.substr(0, 10));
+
+  // offset should have moved
+  AssertRead(file, 10, str.substr(10, 10));
+}
+
+TEST_F(BufferedPseudoFileTest, Write) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertWrite(file, "It");
+
+  // offset should have moved
+  AssertWrite(file, " is");
+
+  const std::string updated_str = "It isis a test string";
+
+  AssertReadAt(file, 0, 100, updated_str);
+
+  // make sure file was not updated before conenction was closed.
+  ASSERT_EQ(file_wrapper.buffer(), str);
+
+  CloseFile(file);
+
+  // make sure file was updated
+  AssertFileWrapperState(file_wrapper, updated_str);
+}
+
+TEST_F(BufferedPseudoFileTest, Truncate) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertTruncate(file, 10);
+
+  AssertRead(file, 100, str.substr(0, 10));
+
+  // make sure file was not updated before conenction was closed.
+  ASSERT_EQ(file_wrapper.buffer(), str);
+
+  CloseFile(file);
+
+  // make sure file was updated
+  AssertFileWrapperState(file_wrapper, str.substr(0, 10));
+}
+
+TEST_F(BufferedPseudoFileTest, SeekFromStart) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
+  auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertSeek(file, 5, fuchsia::io::SeekOrigin::START, 5);
+
+  AssertRead(file, 100, str.substr(5));
+}
+
+TEST_F(BufferedPseudoFileTest, SeekFromCurent) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
+  auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertSeek(file, 5, fuchsia::io::SeekOrigin::START, 5);
+
+  AssertSeek(file, 10, fuchsia::io::SeekOrigin::CURRENT, 15);
+
+  AssertRead(file, 100, str.substr(15));
+}
+
+TEST_F(BufferedPseudoFileTest, SeekFromEnd) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
+  auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertSeek(file, -2, fuchsia::io::SeekOrigin::END, str.length() - 2);
+
+  AssertRead(file, 100, str.substr(str.length() - 2));
+}
+
+TEST_F(BufferedPseudoFileTest, SeekFromEndWith0Offset) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
+  auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertSeek(file, 0, fuchsia::io::SeekOrigin::END, str.length());
+
+  AssertRead(file, 100, "");
+}
+
+TEST_F(BufferedPseudoFileTest, SeekFailsIfOffsetMoreThanCapacity) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
+  auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertSeek(file, 1, fuchsia::io::SeekOrigin::END, 0, ZX_ERR_OUT_OF_RANGE);
+
+  // make sure offset didnot change
+  AssertRead(file, 100, str);
+}
+
+TEST_F(BufferedPseudoFileTest, WriteafterEndOfFile) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertSeek(file, 5, fuchsia::io::SeekOrigin::END, str.length() + 5);
+
+  AssertWrite(file, "is");
+
+  auto updated_str = str + "\0\0\0\0\0is";
+
+  AssertReadAt(file, 0, 100, updated_str);
+
+  // make sure file was not updated before conenction was closed.
+  ASSERT_EQ(file_wrapper.buffer(), str);
+
+  CloseFile(file);
+
+  // make sure file was updated
+  AssertFileWrapperState(file_wrapper, updated_str);
+}
+
+TEST_F(BufferedPseudoFileTest, WriteFailsForReadOnly) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertWrite(file, "is", ZX_ERR_ACCESS_DENIED, 0);
+
+  CloseFile(file);
+
+  // make sure file was not updated
+  AssertFileWrapperState(file_wrapper, str);
+}
+
+TEST_F(BufferedPseudoFileTest, WriteAtFailsForReadOnly) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertWriteAt(file, "is", 0, ZX_ERR_ACCESS_DENIED, 0);
+
+  CloseFile(file);
+
+  // make sure file was not updated
+  AssertFileWrapperState(file_wrapper, str);
+}
+
+TEST_F(BufferedPseudoFileTest, TruncateFailsForReadOnly) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
+
+  AssertTruncate(file, 10, ZX_ERR_ACCESS_DENIED);
+
+  CloseFile(file);
+
+  // make sure file was not updated
+  AssertFileWrapperState(file_wrapper, str);
+}
+
+TEST_F(BufferedPseudoFileTest, ReadAtFailsForWriteOnly) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenFile(file_wrapper.file(), fuchsia::io::OPEN_RIGHT_WRITABLE,
+                       file_wrapper.dispatcher());
+
+  AssertReadAt(file, 0, 10, "", ZX_ERR_ACCESS_DENIED);
+}
+
+TEST_F(BufferedPseudoFileTest, ReadFailsForWriteOnly) {
+  const std::string str = "this is a test string";
+  auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
+  auto file = OpenFile(file_wrapper.file(), fuchsia::io::OPEN_RIGHT_WRITABLE,
+                       file_wrapper.dispatcher());
+
+  AssertRead(file, 10, "", ZX_ERR_ACCESS_DENIED);
 }
 
 }  // namespace