text:
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <map>
#include <string>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/files/scoped_temp_dir.h"
#include "base/guid.h"
#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/invalidation/invalidation_service_factory.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/signin/fake_profile_oauth2_token_service.h"
#include "chrome/browser/signin/fake_profile_oauth2_token_service_wrapper.h"
#include "chrome/browser/signin/profile_oauth2_token_service.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_manager.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
#include "chrome/browser/sync/glue/device_info.h"
#include "chrome/browser/sync/glue/session_change_processor.h"
#include "chrome/browser/sync/glue/session_data_type_controller.h"
#include "chrome/browser/sync/glue/session_model_associator.h"
#include "chrome/browser/sync/glue/session_sync_test_helper.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/glue/synced_device_tracker.h"
#include "chrome/browser/sync/glue/synced_tab_delegate.h"
#include "chrome/browser/sync/glue/tab_node_pool.h"
#include "chrome/browser/sync/profile_sync_components_factory_mock.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/profile_sync_test_util.h"
#include "chrome/browser/sync/test_profile_sync_service.h"
#include "chrome/browser/ui/sync/tab_contents_synced_tab_delegate.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_browser_thread.h"
#include "google_apis/gaia/gaia_constants.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "sync/internal_api/public/base/model_type.h"
#include "sync/internal_api/public/change_record.h"
#include "sync/internal_api/public/read_node.h"
#include "sync/internal_api/public/read_transaction.h"
#include "sync/internal_api/public/test/test_user_share.h"
#include "sync/internal_api/public/write_node.h"
#include "sync/internal_api/public/write_transaction.h"
#include "sync/protocol/session_specifics.pb.h"
#include "sync/protocol/sync.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ui_base_types.h"
#include "url/gurl.h"

using browser_sync::SessionChangeProcessor;
using browser_sync::SessionDataTypeController;
using browser_sync::SessionModelAssociator;
using browser_sync::SyncBackendHost;
using content::BrowserThread;
using content::WebContents;
using syncer::ChangeRecord;
using testing::_;
using testing::Return;

namespace browser_sync {

namespace {

class FakeProfileSyncService : public TestProfileSyncService {
 public:
  FakeProfileSyncService(
      ProfileSyncComponentsFactory* factory,
      Profile* profile,
      SigninManagerBase* signin,
      ProfileOAuth2TokenService* oauth2_token_service,
      ProfileSyncService::StartBehavior behavior)
      : TestProfileSyncService(factory,
                               profile,
                               signin,
                               oauth2_token_service,
                               behavior) {}
  virtual ~FakeProfileSyncService() {}

  virtual scoped_ptr<DeviceInfo> GetLocalDeviceInfo() const OVERRIDE {
    return scoped_ptr<DeviceInfo>(new DeviceInfo(base::GenerateGUID(),
                                                 "client_name",
                                                 std::string(),
                                                 std::string(),
                                                 sync_pb::SyncEnums::TYPE_WIN));
  }
};

bool CompareMemoryToString(
    const std::string& str,
    const scoped_refptr<base::RefCountedMemory>& mem) {
  if (mem->size() != str.size())
    return false;
  for (size_t i = 0; i <mem->size(); ++i) {
    if (str[i] != *(mem->front() + i))
      return false;
  }
  return true;
}

ACTION_P(ReturnSyncBackendHost, callback) {
  return new browser_sync::SyncBackendHostForProfileSyncTest(
      arg1, arg2, callback);
}

}  // namespace

class ProfileSyncServiceSessionTest
    : public BrowserWithTestWindowTest,
      public content::NotificationObserver {
 public:
  ProfileSyncServiceSessionTest()
      : window_bounds_(0, 1, 2, 3),
        notified_of_refresh_(false),
        notified_of_update_(false) {}
  ProfileSyncService* sync_service() { return sync_service_.get(); }

 protected:
  virtual TestingProfile* CreateProfile() OVERRIDE {
    TestingProfile::Builder builder;
    builder.AddTestingFactory(
        ProfileOAuth2TokenServiceFactory::GetInstance(),
        FakeProfileOAuth2TokenServiceWrapper::BuildAutoIssuingTokenService);
    // Don't want the profile to create a real ProfileSyncService.
    builder.AddTestingFactory(ProfileSyncServiceFactory::GetInstance(), NULL);
    scoped_ptr<TestingProfile> profile(builder.Build());
    invalidation::InvalidationServiceFactory::GetInstance()->
        SetBuildOnlyFakeInvalidatorsForTest(true);
    return profile.release();
  }

  virtual void SetUp() {
    // BrowserWithTestWindowTest implementation.
    BrowserWithTestWindowTest::SetUp();
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
        content::NotificationService::AllSources());
    registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
        content::NotificationService::AllSources());
  }

  virtual void Observe(int type,
      const content::NotificationSource& source,
      const content::NotificationDetails& details) OVERRIDE {
    switch (type) {
      case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
        notified_of_update_ = true;
        break;
      case chrome::NOTIFICATION_SYNC_REFRESH_LOCAL:
        notified_of_refresh_ = true;
        break;
      default:
        NOTREACHED();
        break;
    }
  }

  virtual void TearDown() {
    sync_service_->Shutdown();
    sync_service_.reset();

    // We need to destroy the profile before shutting down the threads, because
    // some of the ref counted objects in the profile depend on their
    // destruction on the io thread.
    DestroyBrowserAndProfile();
    ASSERT_FALSE(profile());

    // Pump messages posted by the sync core thread (which may end up
    // posting on the IO thread).
    base::RunLoop().RunUntilIdle();
    BrowserWithTestWindowTest::TearDown();
  }

  bool StartSyncService(const base::Closure& callback,
                        bool will_fail_association) {
    if (sync_service_)
      return false;
    SigninManagerBase* signin =
        SigninManagerFactory::GetForProfile(profile());
    signin->SetAuthenticatedUsername("test_user");
    ProfileOAuth2TokenService* oauth2_token_service =
        ProfileOAuth2TokenServiceFactory::GetForProfile(profile());
    ProfileSyncComponentsFactoryMock* factory =
        new ProfileSyncComponentsFactoryMock();
    sync_service_.reset(new FakeProfileSyncService(
        factory,
        profile(),
        signin,
        oauth2_token_service,
        ProfileSyncService::AUTO_START));
    EXPECT_CALL(*factory, CreateSyncBackendHost(_,_,_)).
        WillOnce(ReturnSyncBackendHost(callback));

    // Register the session data type.
    SessionDataTypeController *dtc = new SessionDataTypeController(factory,
                                         profile(),
                                         sync_service_.get());
    sync_service_->RegisterDataTypeController(dtc);

    model_associator_ =
        new SessionModelAssociator(sync_service_.get(),
                                   true /* setup_for_test */);
    change_processor_ = new SessionChangeProcessor(
        dtc, model_associator_,
        true /* setup_for_test */);
    EXPECT_CALL(*factory, CreateSessionSyncComponents(_, _)).
        WillOnce(Return(ProfileSyncComponentsFactory::SyncComponents(
            model_associator_, change_processor_)));
    EXPECT_CALL(*factory, CreateDataTypeManager(_, _, _, _, _, _)).
        WillOnce(ReturnNewDataTypeManager());

    ProfileOAuth2TokenServiceFactory::GetForProfile(profile())
        ->UpdateCredentials("test_user", "oauth2_login_token");
    sync_service_->Initialize();
    base::MessageLoop::current()->Run();
    return true;
  }

  // Path used in testing.
  base::ScopedTempDir temp_dir_;
  SessionModelAssociator* model_associator_;
  SessionChangeProcessor* change_processor_;
  SessionID window_id_;
  scoped_ptr<TestProfileSyncService> sync_service_;
  const gfx::Rect window_bounds_;
  bool notified_of_refresh_;
  bool notified_of_update_;
  content::NotificationRegistrar registrar_;
  net::TestURLFetcherFactory fetcher_factory_;
  SessionSyncTestHelper helper_;
};

class CreateRootHelper {
 public:
  explicit CreateRootHelper(ProfileSyncServiceSessionTest* test)
      : callback_(base::Bind(&CreateRootHelper::CreateRootCallback,
                             base::Unretained(this), test)),
        success_(false) {
  }

  virtual ~CreateRootHelper() {}

  const base::Closure& callback() const { return callback_; }
  bool success() { return success_; }

 private:
  void CreateRootCallback(ProfileSyncServiceSessionTest* test) {
    success_ = syncer::TestUserShare::CreateRoot(
        syncer::SESSIONS, test->sync_service()->GetUserShare());
  }

  base::Closure callback_;
  bool success_;
};

// Test that we can write this machine's session to a node and retrieve it.
TEST_F(ProfileSyncServiceSessionTest, WriteSessionToNode) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Check that the DataTypeController associated the models.
  bool has_nodes;
  ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes));
  ASSERT_TRUE(has_nodes);
  std::string machine_tag = model_associator_->GetCurrentMachineTag();
  int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag);
  ASSERT_NE(syncer::kInvalidId, sync_id);

  // Check that we can get the correct session specifics back from the node.
  syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
  syncer::ReadNode node(&trans);
  ASSERT_EQ(syncer::BaseNode::INIT_OK,
            node.InitByClientTagLookup(syncer::SESSIONS, machine_tag));
  const sync_pb::SessionSpecifics& specifics(node.GetSessionSpecifics());
  ASSERT_EQ(machine_tag, specifics.session_tag());
  ASSERT_TRUE(specifics.has_header());
  const sync_pb::SessionHeader& header_s = specifics.header();
  ASSERT_TRUE(header_s.has_device_type());
  ASSERT_EQ("client_name", header_s.client_name());
  ASSERT_EQ(0, header_s.window_size());
}

// Crashes sometimes on Windows, particularly XP.
// See http://crbug.com/174951
#if defined(OS_WIN)
#define MAYBE_WriteFilledSessionToNode DISABLED_WriteFilledSessionToNode
#else
#define MAYBE_WriteFilledSessionToNode WriteFilledSessionToNode
#endif  // defined(OS_WIN)

// Test that we can fill this machine's session, write it to a node,
// and then retrieve it.
TEST_F(ProfileSyncServiceSessionTest, MAYBE_WriteFilledSessionToNode) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Check that the DataTypeController associated the models.
  bool has_nodes;
  ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes));
  ASSERT_TRUE(has_nodes);
  AddTab(browser(), GURL("http://foo/1"));
  NavigateAndCommitActiveTab(GURL("http://foo/2"));
  AddTab(browser(), GURL("http://bar/1"));
  NavigateAndCommitActiveTab(GURL("http://bar/2"));

  ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes));
  ASSERT_TRUE(has_nodes);
  std::string machine_tag = model_associator_->GetCurrentMachineTag();
  int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag);
  ASSERT_NE(syncer::kInvalidId, sync_id);

  // Check that this machine's data is not included in the foreign windows.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(foreign_sessions.size(), 0U);

  // Get the tabs for this machine from the node and check that they were
  // filled.
  SessionModelAssociator::TabLinksMap tab_map =
      model_associator_->local_tab_map_;
  ASSERT_EQ(2U, tab_map.size());
  // Tabs are ordered by sessionid in tab_map, so should be able to traverse
  // the tree based on order of tabs created
  SessionModelAssociator::TabLinksMap::iterator iter = tab_map.begin();
  ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
  ASSERT_EQ(GURL("http://foo/1"), iter->second->tab()->
          GetEntryAtIndex(0)->GetVirtualURL());
  ASSERT_EQ(GURL("http://foo/2"), iter->second->tab()->
          GetEntryAtIndex(1)->GetVirtualURL());
  iter++;
  ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
  ASSERT_EQ(GURL("http://bar/1"), iter->second->tab()->
      GetEntryAtIndex(0)->GetVirtualURL());
  ASSERT_EQ(GURL("http://bar/2"), iter->second->tab()->
      GetEntryAtIndex(1)->GetVirtualURL());
}

// Test that we fail on a failed model association.
TEST_F(ProfileSyncServiceSessionTest, FailModelAssociation) {
  ASSERT_TRUE(StartSyncService(base::Closure(), true));
  ASSERT_TRUE(sync_service_->HasUnrecoverableError());
}

// Write a foreign session to a node, and then retrieve it.
TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Check that the DataTypeController associated the models.
  bool has_nodes;
  ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes));
  ASSERT_TRUE(has_nodes);

  // Fill an instance of session specifics with a foreign session's data.
  std::string tag = "tag1";
  SessionID::id_type nums1[] = {5, 10, 13, 17};
  std::vector<sync_pb::SessionSpecifics> tabs1;
  std::vector<SessionID::id_type> tab_list1(nums1, nums1 + arraysize(nums1));
  sync_pb::SessionSpecifics meta(helper_.BuildForeignSession(
      tag, tab_list1, &tabs1));

  // Update associator with the session's meta node containing one window.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());
  // Add tabs for the window.
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
       iter != tabs1.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, base::Time());
  }

  // Check that the foreign session was associated and retrieve the data.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  std::vector<std::vector<SessionID::id_type> > session_reference;
  session_reference.push_back(tab_list1);
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
}

// Write a foreign session with one window to a node. Sync, then add a window.
// Sync, then add a third window. Close the two windows.
TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeThreeWindows) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Build a foreign session with one window and four tabs.
  std::string tag = "tag1";
  SessionID::id_type nums1[] = {5, 10, 13, 17};
  std::vector<sync_pb::SessionSpecifics> tabs1;
  std::vector<SessionID::id_type> tab_list1(nums1, nums1 + arraysize(nums1));
  sync_pb::SessionSpecifics meta(helper_.BuildForeignSession(
      tag, tab_list1, &tabs1));
  // Update associator with the session's meta node containing one window.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());
  // Add tabs for first window.
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
       iter != tabs1.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, base::Time());
  }

  // Verify first window
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  std::vector<std::vector<SessionID::id_type> > session_reference;
  session_reference.push_back(tab_list1);
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));

  // Add a second window.
  SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
  std::vector<SessionID::id_type> tab_list2(
      tab_nums2, tab_nums2 + arraysize(tab_nums2));
  helper_.AddWindowSpecifics(1, tab_list2, &meta);
  std::vector<sync_pb::SessionSpecifics> tabs2;
  tabs2.resize(tab_list2.size());
  for (size_t i = 0; i < tab_list2.size(); ++i) {
    helper_.BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]);
  }
  // Update associator with the session's meta node containing two windows.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());
  // Add tabs for second window.
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs2.begin();
       iter != tabs2.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, base::Time());
  }

  // Verify the two windows.
  foreign_sessions.clear();
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  session_reference.push_back(tab_list2);
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));

  // Add a third window.
  SessionID::id_type tab_nums3[] = {8, 16, 19, 21};
  std::vector<SessionID::id_type> tab_list3(
      tab_nums3, tab_nums3 + arraysize(tab_nums3));
  helper_.AddWindowSpecifics(2, tab_list3, &meta);
  std::vector<sync_pb::SessionSpecifics> tabs3;
  tabs3.resize(tab_list3.size());
  for (size_t i = 0; i < tab_list3.size(); ++i) {
    helper_.BuildTabSpecifics(tag, 0, tab_list3[i], &tabs3[i]);
  }
  // Update associator with the session's meta node containing three windows.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());
  // Add tabs for third window.
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs3.begin();
       iter != tabs3.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, base::Time());
  }

  // Verify the three windows
  foreign_sessions.clear();
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  session_reference.push_back(tab_list3);
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));

  // Close third window (by clearing and then not adding it back).
  meta.mutable_header()->clear_window();
  helper_.AddWindowSpecifics(0, tab_list1, &meta);
  helper_.AddWindowSpecifics(1, tab_list2, &meta);
  // Update associator with just the meta node, now containing only two windows.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());

  // Verify first two windows are still there.
  foreign_sessions.clear();
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  session_reference.pop_back();  // Pop off the data for the third window.
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));

  // Close second window (by clearing and then not adding it back).
  meta.mutable_header()->clear_window();
  helper_.AddWindowSpecifics(0, tab_list1, &meta);
  // Update associator with just the meta node, now containing only one windows.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());

  // Verify first window is still there.
  foreign_sessions.clear();
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  session_reference.pop_back();  // Pop off the data for the second window.
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
}

// Write a foreign session to a node, with the tabs arriving first, and then
// retrieve it.
TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeTabsFirst) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Fill an instance of session specifics with a foreign session's data.
  std::string tag = "tag1";
  SessionID::id_type nums1[] = {5, 10, 13, 17};
  std::vector<sync_pb::SessionSpecifics> tabs1;
  std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
  sync_pb::SessionSpecifics meta(helper_.BuildForeignSession(
      tag, tab_list1, &tabs1));

  // Add tabs for first window.
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
       iter != tabs1.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, base::Time());
  }
  // Update associator with the session's meta node containing one window.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());

  // Check that the foreign session was associated and retrieve the data.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  std::vector<std::vector<SessionID::id_type> > session_reference;
  session_reference.push_back(tab_list1);
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
}

// Write a foreign session to a node with some tabs that never arrive.
TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNodeMissingTabs) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Fill an instance of session specifics with a foreign session's data.
  std::string tag = "tag1";
  SessionID::id_type nums1[] = {5, 10, 13, 17};
  std::vector<sync_pb::SessionSpecifics> tabs1;
  std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
  sync_pb::SessionSpecifics meta(helper_.BuildForeignSession(
      tag, tab_list1, &tabs1));
  // Add a second window, but this time only create two tab nodes, despite the
  // window expecting four tabs.
  SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
  std::vector<SessionID::id_type> tab_list2(
      tab_nums2, tab_nums2 + arraysize(tab_nums2));
  helper_.AddWindowSpecifics(1, tab_list2, &meta);
  std::vector<sync_pb::SessionSpecifics> tabs2;
  tabs2.resize(2);
  for (size_t i = 0; i < 2; ++i) {
    helper_.BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]);
  }

  // Update associator with the session's meta node containing two windows.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());
  // Add tabs for first window.
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
       iter != tabs1.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, base::Time());
  }
  // Add tabs for second window.
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs2.begin();
       iter != tabs2.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, base::Time());
  }

  // Check that the foreign session was associated and retrieve the data.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  ASSERT_EQ(2U, foreign_sessions[0]->windows.size());
  ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
  ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size());

  // Close the second window.
  meta.mutable_header()->clear_window();
  helper_.AddWindowSpecifics(0, tab_list1, &meta);

  // Update associator with the session's meta node containing one window.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());

  // Check that the foreign session was associated and retrieve the data.
  foreign_sessions.clear();
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  ASSERT_EQ(1U, foreign_sessions[0]->windows.size());
  std::vector<std::vector<SessionID::id_type> > session_reference;
  session_reference.push_back(tab_list1);
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
}

// Test the DataTypeController on update.
TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionUpdate) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());
  int64 node_id = model_associator_->GetSyncIdFromSessionTag(
      model_associator_->GetCurrentMachineTag());
  ASSERT_FALSE(notified_of_update_);
  {
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    change_processor_->ApplyChangesFromSyncModel(
        &trans, 0,
        ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList(
            node_id, ChangeRecord::ACTION_UPDATE));
  }
  ASSERT_TRUE(notified_of_update_);
}

// Test the DataTypeController on add.
TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionAdd) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  int64 node_id = model_associator_->GetSyncIdFromSessionTag(
      model_associator_->GetCurrentMachineTag());
  ASSERT_FALSE(notified_of_update_);
  {
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    change_processor_->ApplyChangesFromSyncModel(
        &trans, 0,
        ProfileSyncServiceTestHelper::MakeSingletonChangeRecordList(
            node_id, ChangeRecord::ACTION_ADD));
  }
  ASSERT_TRUE(notified_of_update_);
}

// Test the DataTypeController on delete.
TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionDelete) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  int64 node_id = model_associator_->GetSyncIdFromSessionTag(
      model_associator_->GetCurrentMachineTag());
  sync_pb::EntitySpecifics deleted_specifics;
  deleted_specifics.mutable_session()->set_session_tag("tag");
  ASSERT_FALSE(notified_of_update_);
  {
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    change_processor_->ApplyChangesFromSyncModel(
        &trans, 0,
        ProfileSyncServiceTestHelper::MakeSingletonDeletionChangeRecordList(
            node_id, deleted_specifics));
  }
  ASSERT_TRUE(notified_of_update_);
}
// Test the TabNodePool when it starts off empty.
TEST_F(ProfileSyncServiceSessionTest, TabNodePoolEmpty) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  std::vector<int> node_ids;
  ASSERT_EQ(0U, model_associator_->local_tab_pool_.Capacity());
  ASSERT_TRUE(model_associator_->local_tab_pool_.Empty());
  ASSERT_TRUE(model_associator_->local_tab_pool_.Full());
  const size_t num_ids = 10;
  for (size_t i = 0; i < num_ids; ++i) {
    int id = model_associator_->local_tab_pool_.GetFreeTabNode();
    ASSERT_GT(id, TabNodePool::kInvalidTabNodeID);
    node_ids.push_back(id);
    // Associate with a tab node.
    model_associator_->local_tab_pool_.AssociateTabNode(id, i + 1);
  }
  ASSERT_EQ(num_ids, model_associator_->local_tab_pool_.Capacity());
  ASSERT_TRUE(model_associator_->local_tab_pool_.Empty());
  ASSERT_FALSE(model_associator_->local_tab_pool_.Full());
  for (size_t i = 0; i < num_ids; ++i) {
    model_associator_->local_tab_pool_.FreeTabNode(node_ids[i]);
  }
  ASSERT_EQ(num_ids, model_associator_->local_tab_pool_.Capacity());
  ASSERT_FALSE(model_associator_->local_tab_pool_.Empty());
  ASSERT_TRUE(model_associator_->local_tab_pool_.Full());
}

// TODO(jhorwich): Re-enable when crbug.com/121487 addressed
TEST_F(ProfileSyncServiceSessionTest, TabNodePoolNonEmpty) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  const size_t num_starting_nodes = 3;
  for (size_t i = 0; i < num_starting_nodes; ++i) {
    size_t node_id = i + 1;
    model_associator_->local_tab_pool_.AddTabNode(node_id);
    model_associator_->local_tab_pool_.AssociateTabNode(node_id, i);
    model_associator_->local_tab_pool_.FreeTabNode(node_id);
  }

  std::vector<int> node_ids;
  ASSERT_EQ(num_starting_nodes, model_associator_->local_tab_pool_.Capacity());
  ASSERT_FALSE(model_associator_->local_tab_pool_.Empty());
  ASSERT_TRUE(model_associator_->local_tab_pool_.Full());
  const size_t num_ids = 10;
  for (size_t i = 0; i < num_ids; ++i) {
    int id = model_associator_->local_tab_pool_.GetFreeTabNode();
    ASSERT_GT(id, TabNodePool::kInvalidTabNodeID);
    node_ids.push_back(id);
    // Associate with a tab node.
    model_associator_->local_tab_pool_.AssociateTabNode(id, i + 1);
  }
  ASSERT_EQ(num_ids, model_associator_->local_tab_pool_.Capacity());
  ASSERT_TRUE(model_associator_->local_tab_pool_.Empty());
  ASSERT_FALSE(model_associator_->local_tab_pool_.Full());
  for (size_t i = 0; i < num_ids; ++i) {
    model_associator_->local_tab_pool_.FreeTabNode(node_ids[i]);
  }
  ASSERT_EQ(num_ids, model_associator_->local_tab_pool_.Capacity());
  ASSERT_FALSE(model_associator_->local_tab_pool_.Empty());
  ASSERT_TRUE(model_associator_->local_tab_pool_.Full());
}

// Write a foreign session to a node, and then delete it.
TEST_F(ProfileSyncServiceSessionTest, DeleteForeignSession) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Check that the DataTypeController associated the models.
  bool has_nodes;
  ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes));
  ASSERT_TRUE(has_nodes);

  // A foreign session's tag.
  std::string tag = "tag1";

  // Should do nothing if the foreign session doesn't exist.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_FALSE(notified_of_update_);
  model_associator_->DeleteForeignSession(tag);
  ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  // Verify that deleteForeignSession did not trigger the
  // NOTIFICATION_FOREIGN_SESSION_DISABLED notification.
  ASSERT_FALSE(notified_of_update_);

  // Fill an instance of session specifics with a foreign session's data.
  SessionID::id_type nums1[] = {5, 10, 13, 17};
  std::vector<sync_pb::SessionSpecifics> tabs1;
  std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
  sync_pb::SessionSpecifics meta(helper_.BuildForeignSession(
      tag, tab_list1, &tabs1));

  // Update associator with the session's meta node containing one window.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());
  // Add tabs for the window.
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
       iter != tabs1.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, base::Time());
  }

  // Check that the foreign session was associated and retrieve the data.
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  std::vector<std::vector<SessionID::id_type> > session_reference;
  session_reference.push_back(tab_list1);
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));

  ASSERT_FALSE(notified_of_update_);
  // Now delete the foreign session.
  model_associator_->DeleteForeignSession(tag);
  ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));

  // Verify that deleteForeignSession triggers the
  // NOTIFICATION_FOREIGN_SESSION_DISABLED notification.
  ASSERT_TRUE(notified_of_update_);
}

// Associate both a non-stale foreign session and a stale foreign session.
// Ensure only the stale session gets deleted.
TEST_F(ProfileSyncServiceSessionTest, DeleteStaleSessions) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Fill two instances of session specifics with a foreign session's data.
  std::string tag = "tag1";
  SessionID::id_type nums1[] = {5, 10, 13, 17};
  std::vector<sync_pb::SessionSpecifics> tabs1;
  std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
  sync_pb::SessionSpecifics meta(helper_.BuildForeignSession(
      tag, tab_list1, &tabs1));
  std::string tag2 = "tag2";
  sync_pb::SessionSpecifics meta2;
  helper_.BuildSessionSpecifics(tag2, &meta2);
  SessionID::id_type tab_nums2[] = {8, 15, 18, 20};
  std::vector<SessionID::id_type> tab_list2(
      tab_nums2, tab_nums2 + arraysize(tab_nums2));
  helper_.AddWindowSpecifics(0, tab_list2, &meta2);
  std::vector<sync_pb::SessionSpecifics> tabs2;
  tabs2.resize(tab_list2.size());
  for (size_t i = 0; i < tab_list2.size(); ++i) {
    helper_.BuildTabSpecifics(tag2, 0, tab_list2[i], &tabs2[i]);
  }

  // Set the modification time for tag1 to be 21 days ago, tag2 to 5 days ago.
  base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21);
  base::Time tag2_time = base::Time::Now() - base::TimeDelta::FromDays(5);

  // Associate specifics.
  model_associator_->AssociateForeignSpecifics(meta, tag1_time);
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
       iter != tabs1.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, tag1_time);
  }
  model_associator_->AssociateForeignSpecifics(meta2, tag2_time);
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs2.begin();
       iter != tabs2.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, tag2_time);
  }

  // Check that the foreign session was associated and retrieve the data.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(2U, foreign_sessions.size());

  // Now delete the stale session and verify the non-stale one is still there.
  model_associator_->DeleteStaleSessions();
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  std::vector<std::vector<SessionID::id_type> > session_reference;
  session_reference.push_back(tab_list2);
  helper_.VerifySyncedSession(tag2, session_reference, *(foreign_sessions[0]));
}

// Write a stale foreign session to a node. Then update one of its tabs so
// the session is no longer stale. Ensure it doesn't get deleted.
TEST_F(ProfileSyncServiceSessionTest, StaleSessionRefresh) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  std::string tag = "tag1";
  SessionID::id_type nums1[] = {5, 10, 13, 17};
  std::vector<sync_pb::SessionSpecifics> tabs1;
  std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
  sync_pb::SessionSpecifics meta(helper_.BuildForeignSession(
      tag, tab_list1, &tabs1));

  // Associate.
  base::Time stale_time = base::Time::Now() - base::TimeDelta::FromDays(21);
  model_associator_->AssociateForeignSpecifics(meta, stale_time);
  for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs1.begin();
       iter != tabs1.end(); ++iter) {
    model_associator_->AssociateForeignSpecifics(*iter, stale_time);
  }

  // Associate one of the tabs with a non-stale time.
  model_associator_->AssociateForeignSpecifics(tabs1[0], base::Time::Now());

  // Check that the foreign session was associated and retrieve the data.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());

  // Verify the now non-stale session does not get deleted.
  model_associator_->DeleteStaleSessions();
  ASSERT_TRUE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(1U, foreign_sessions.size());
  std::vector<std::vector<SessionID::id_type> > session_reference;
  session_reference.push_back(tab_list1);
  helper_.VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
}

// Crashes sometimes on Windows, particularly XP.
// See http://crbug.com/174951
#if defined(OS_WIN)
#define MAYBE_ValidTabs DISABLED_ValidTabs
#else
#define MAYBE_ValidTabs ValidTabs
#endif  // defined(OS_WIN)

// Test that tabs with nothing but "chrome://*" and "file://*" navigations are
// not be synced.
TEST_F(ProfileSyncServiceSessionTest, MAYBE_ValidTabs) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  AddTab(browser(), GURL("chrome://bla1/"));
  NavigateAndCommitActiveTab(GURL("chrome://bla2"));
  AddTab(browser(), GURL("file://bla3/"));
  AddTab(browser(), GURL("bla://bla"));
  // Note: chrome://newtab has special handling which crashes in unit tests.

  // Get the tabs for this machine. Only the bla:// url should be synced.
  SessionModelAssociator::TabLinksMap tab_map =
      model_associator_->local_tab_map_;
  ASSERT_EQ(1U, tab_map.size());
  SessionModelAssociator::TabLinksMap::iterator iter = tab_map.begin();
  ASSERT_EQ(1, iter->second->tab()->GetEntryCount());
  ASSERT_EQ(GURL("bla://bla"), iter->second->tab()->
      GetEntryAtIndex(0)->GetVirtualURL());
}

// Verify that AttemptSessionsDataRefresh triggers the
// NOTIFICATION_SYNC_REFRESH_LOCAL notification.
// TODO(zea): Once we can have unit tests that are able to open to the NTP,
// test that the NTP/#opentabs URL triggers a refresh as well (but only when
// it is the active tab).
TEST_F(ProfileSyncServiceSessionTest, SessionsRefresh) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Empty, so returns false.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_FALSE(notified_of_refresh_);
  model_associator_->AttemptSessionsDataRefresh();
  ASSERT_TRUE(notified_of_refresh_);

  // Nothing should have changed since we don't have unapplied data.
  ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
}

// Ensure model association associates the pre-existing tabs.
TEST_F(ProfileSyncServiceSessionTest, ExistingTabs) {
  AddTab(browser(), GURL("http://foo1"));
  NavigateAndCommitActiveTab(GURL("http://foo2"));
  AddTab(browser(), GURL("http://bar1"));
  NavigateAndCommitActiveTab(GURL("http://bar2"));

  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());
  bool has_nodes;
  ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes));
  ASSERT_TRUE(has_nodes);

  std::string machine_tag = model_associator_->GetCurrentMachineTag();
  int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag);
  ASSERT_NE(syncer::kInvalidId, sync_id);

  // Check that this machine's data is not included in the foreign windows.
  std::vector<const SyncedSession*> foreign_sessions;
  ASSERT_FALSE(model_associator_->GetAllForeignSessions(&foreign_sessions));
  ASSERT_EQ(foreign_sessions.size(), 0U);

  // Get the tabs for this machine from the node and check that they were
  // filled.
  SessionModelAssociator::TabLinksMap tab_map =
      model_associator_->local_tab_map_;
  ASSERT_EQ(2U, tab_map.size());
  // Tabs are ordered by sessionid in tab_map, so should be able to traverse
  // the tree based on order of tabs created
  SessionModelAssociator::TabLinksMap::iterator iter = tab_map.begin();
  ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
  ASSERT_EQ(GURL("http://foo1"), iter->second->tab()->
          GetEntryAtIndex(0)->GetVirtualURL());
  ASSERT_EQ(GURL("http://foo2"), iter->second->tab()->
          GetEntryAtIndex(1)->GetVirtualURL());
  iter++;
  ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
  ASSERT_EQ(GURL("http://bar1"), iter->second->tab()->
      GetEntryAtIndex(0)->GetVirtualURL());
  ASSERT_EQ(GURL("http://bar2"), iter->second->tab()->
      GetEntryAtIndex(1)->GetVirtualURL());
}

TEST_F(ProfileSyncServiceSessionTest, MissingHeaderAndTab) {
  AddTab(browser(), GURL("http://foo1"));
  NavigateAndCommitActiveTab(GURL("http://foo2"));
  AddTab(browser(), GURL("http://bar1"));
  NavigateAndCommitActiveTab(GURL("http://bar2"));
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  syncer::SyncError error;
  std::string local_tag = model_associator_->GetCurrentMachineTag();

  error = model_associator_->DisassociateModels();
  ASSERT_FALSE(error.IsSet());
  {
    // Create a sync node with the local tag but neither header nor tab field.
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    syncer::ReadNode root(&trans);
    root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS));
    syncer::WriteNode extra_header(&trans);
    syncer::WriteNode::InitUniqueByCreationResult result =
        extra_header.InitUniqueByCreation(syncer::SESSIONS, root, "new_tag");
    ASSERT_EQ(syncer::WriteNode::INIT_SUCCESS, result);
    sync_pb::SessionSpecifics specifics;
    specifics.set_session_tag(local_tag);
    extra_header.SetSessionSpecifics(specifics);
  }

  error = model_associator_->AssociateModels(NULL, NULL);
  ASSERT_FALSE(error.IsSet());
}

TEST_F(ProfileSyncServiceSessionTest, MultipleHeaders) {
  AddTab(browser(), GURL("http://foo1"));
  NavigateAndCommitActiveTab(GURL("http://foo2"));
  AddTab(browser(), GURL("http://bar1"));
  NavigateAndCommitActiveTab(GURL("http://bar2"));
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  syncer::SyncError error;
  std::string local_tag = model_associator_->GetCurrentMachineTag();

  error = model_associator_->DisassociateModels();
  ASSERT_FALSE(error.IsSet());
  {
    // Create another sync node with a header field and the local tag.
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    syncer::ReadNode root(&trans);
    root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS));
    syncer::WriteNode extra_header(&trans);
    syncer::WriteNode::InitUniqueByCreationResult result =
        extra_header.InitUniqueByCreation(syncer::SESSIONS,
                                          root, local_tag + "_");
    ASSERT_EQ(syncer::WriteNode::INIT_SUCCESS, result);
    sync_pb::SessionSpecifics specifics;
    specifics.set_session_tag(local_tag);
    specifics.mutable_header();
    extra_header.SetSessionSpecifics(specifics);
  }
  error = model_associator_->AssociateModels(NULL, NULL);
  ASSERT_FALSE(error.IsSet());
}

TEST_F(ProfileSyncServiceSessionTest, CorruptedForeign) {
  AddTab(browser(), GURL("http://foo1"));
  NavigateAndCommitActiveTab(GURL("http://foo2"));
  AddTab(browser(), GURL("http://bar1"));
  NavigateAndCommitActiveTab(GURL("http://bar2"));
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  syncer::SyncError error;

  error = model_associator_->DisassociateModels();
  ASSERT_FALSE(error.IsSet());
  {
    // Create another sync node with neither header nor tab field and a foreign
    // tag.
    std::string foreign_tag = "foreign_tag";
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    syncer::ReadNode root(&trans);
    root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS));
    syncer::WriteNode extra_header(&trans);
    syncer::WriteNode::InitUniqueByCreationResult result =
        extra_header.InitUniqueByCreation(syncer::SESSIONS,
                                          root, foreign_tag);
    ASSERT_EQ(syncer::WriteNode::INIT_SUCCESS, result);
    sync_pb::SessionSpecifics specifics;
    specifics.set_session_tag(foreign_tag);
    extra_header.SetSessionSpecifics(specifics);
  }
  error = model_associator_->AssociateModels(NULL, NULL);
  ASSERT_FALSE(error.IsSet());
}

TEST_F(ProfileSyncServiceSessionTest, MissingLocalTabNode) {
  AddTab(browser(), GURL("http://foo1"));
  NavigateAndCommitActiveTab(GURL("http://foo2"));
  AddTab(browser(), GURL("http://bar1"));
  NavigateAndCommitActiveTab(GURL("http://bar2"));
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  std::string local_tag = model_associator_->GetCurrentMachineTag();
  syncer::SyncError error;

  error = model_associator_->DisassociateModels();
  ASSERT_FALSE(error.IsSet());
  {
    // Delete the first sync tab node.
    std::string tab_tag = TabNodePool::TabIdToTag(local_tag, 1);

    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    syncer::ReadNode root(&trans);
    root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS));
    syncer::WriteNode tab_node(&trans);
    ASSERT_EQ(syncer::BaseNode::INIT_OK,
              tab_node.InitByClientTagLookup(syncer::SESSIONS, tab_tag));
    tab_node.Tombstone();
  }
  error = model_associator_->AssociateModels(NULL, NULL);
  ASSERT_FALSE(error.IsSet());

  // Add some more tabs to ensure we don't conflict with the pre-existing tab
  // node.
  AddTab(browser(), GURL("http://baz1"));
  AddTab(browser(), GURL("http://baz2"));
}

TEST_F(ProfileSyncServiceSessionTest, Favicons) {
    CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Build a foreign session with one window and one tab.
  std::string tag = "tag1";
  sync_pb::SessionSpecifics meta;
  helper_.BuildSessionSpecifics(tag, &meta);
  std::vector<SessionID::id_type> tab_list;
  tab_list.push_back(5);
  helper_.AddWindowSpecifics(0, tab_list, &meta);
  sync_pb::SessionSpecifics tab;
  helper_.BuildTabSpecifics(tag, 0, tab_list[0], &tab);
  std::string url = tab.tab().navigation(0).virtual_url();
  scoped_refptr<base::RefCountedMemory> favicon;

  // Update associator.
  model_associator_->AssociateForeignSpecifics(meta, base::Time());
  model_associator_->AssociateForeignSpecifics(tab, base::Time());
  base::RunLoop().RunUntilIdle();
  ASSERT_FALSE(model_associator_->GetSyncedFaviconForPageURL(url, &favicon));

  // Now add a favicon.
  tab.mutable_tab()->set_favicon_source("http://favicon_source.com/png.ico");
  tab.mutable_tab()->set_favicon_type(sync_pb::SessionTab::TYPE_WEB_FAVICON);
  tab.mutable_tab()->set_favicon("data");
  model_associator_->AssociateForeignSpecifics(tab, base::Time());
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(model_associator_->GetSyncedFaviconForPageURL(url, &favicon));
  ASSERT_TRUE(CompareMemoryToString("data", favicon));

  // Simulate navigating away. The associator should not delete the favicon.
  tab.mutable_tab()->clear_navigation();
  tab.mutable_tab()->add_navigation()->set_virtual_url("http://new_url.com");
  tab.mutable_tab()->clear_favicon_source();
  tab.mutable_tab()->clear_favicon_type();
  tab.mutable_tab()->clear_favicon();
  model_associator_->AssociateForeignSpecifics(tab, base::Time());
  base::RunLoop().RunUntilIdle();
  ASSERT_TRUE(model_associator_->GetSyncedFaviconForPageURL(url, &favicon));
}

TEST_F(ProfileSyncServiceSessionTest, CorruptedLocalHeader) {
  AddTab(browser(), GURL("http://foo1"));
  NavigateAndCommitActiveTab(GURL("http://foo2"));
  AddTab(browser(), GURL("http://bar1"));
  NavigateAndCommitActiveTab(GURL("http://bar2"));
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  std::string local_tag = model_associator_->GetCurrentMachineTag();
  syncer::SyncError error;

  error = model_associator_->DisassociateModels();
  ASSERT_FALSE(error.IsSet());
  {
    // Load the header node and clear it.
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    syncer::WriteNode header(&trans);
    ASSERT_EQ(syncer::BaseNode::INIT_OK,
              header.InitByClientTagLookup(syncer::SESSIONS, local_tag));
    sync_pb::SessionSpecifics specifics;
    header.SetSessionSpecifics(specifics);
  }
  // Ensure we associate properly despite the pre-existing node with our local
  // tag.
  error = model_associator_->AssociateModels(NULL, NULL);
  ASSERT_FALSE(error.IsSet());
}

TEST_F(ProfileSyncServiceSessionTest, CheckPrerenderedWebContentsSwap) {
  AddTab(browser(), GURL("http://foo1"));
  NavigateAndCommitActiveTab(GURL("http://foo2"));
  CreateRootHelper create_root(this);
  // Test setup.
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));

  syncer::SyncError error;
  // Initial association.
  EXPECT_TRUE(model_associator_->AssociateWindows(true, &error));
  ASSERT_FALSE(error.IsSet());

  // To simulate WebContents swap during prerendering, create new WebContents
  // and swap with old WebContents.
  content::WebContents* old_web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  // Create new WebContents, with the required tab helpers.
  WebContents* new_web_contents = WebContents::CreateWithSessionStorage(
      WebContents::CreateParams(profile()),
      old_web_contents->GetController().GetSessionStorageNamespaceMap());
  SessionTabHelper::CreateForWebContents(new_web_contents);
  TabContentsSyncedTabDelegate::CreateForWebContents(new_web_contents);
  new_web_contents->GetController()
      .CopyStateFrom(old_web_contents->GetController());

  // Swap the WebContents.
  int index =
      browser()->tab_strip_model()->GetIndexOfWebContents(old_web_contents);
  browser()->tab_strip_model()->ReplaceWebContentsAt(index, new_web_contents);

  EXPECT_TRUE(model_associator_->AssociateWindows(true, &error));
  ASSERT_FALSE(error.IsSet());
  // Navigate away.
  NavigateAndCommitActiveTab(GURL("http://bar2"));
  EXPECT_TRUE(model_associator_->AssociateWindows(true, &error));
  ASSERT_FALSE(error.IsSet());

  // Delete old WebContents. This should not crash.
  delete old_web_contents;
  EXPECT_TRUE(model_associator_->AssociateWindows(true, &error));
  ASSERT_FALSE(error.IsSet());

  // Try more navigations to make sure everything if fine.
  NavigateAndCommitActiveTab(GURL("http://bar3"));
  EXPECT_TRUE(model_associator_->AssociateWindows(true, &error));
  ASSERT_FALSE(error.IsSet());

  AddTab(browser(), GURL("http://bar4"));
  EXPECT_TRUE(model_associator_->AssociateWindows(true, &error));
  ASSERT_FALSE(error.IsSet());
  NavigateAndCommitActiveTab(GURL("http://bar5"));
  EXPECT_TRUE(model_associator_->AssociateWindows(true, &error));
  ASSERT_FALSE(error.IsSet());
}

TEST_F(ProfileSyncServiceSessionTest, TabPoolFreeNodeLimits) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  ASSERT_TRUE(create_root.success());

  // Allocate TabNodePool::kFreeNodesHighWatermark + 1 nodes and verify that
  // freeing the last node reduces the free node pool size to
  // kFreeNodesLowWatermark.

  SessionID session_id;
  std::vector<int> used_sync_ids;
  for (size_t i = 1; i <= TabNodePool::kFreeNodesHighWatermark + 1; ++i) {
    session_id.set_id(i);
    int sync_id = model_associator_->local_tab_pool_.GetFreeTabNode();
    model_associator_->local_tab_pool_.AssociateTabNode(sync_id, i);
    used_sync_ids.push_back(sync_id);
  }

  // Free all except one node.
  int last_sync_id = used_sync_ids.back();
  used_sync_ids.pop_back();

  for (size_t i = 0; i < used_sync_ids.size(); ++i) {
    model_associator_->local_tab_pool_.FreeTabNode(used_sync_ids[i]);
  }

  // Except one node all nodes should be in FreeNode pool.
  EXPECT_FALSE(model_associator_->local_tab_pool_.Full());
  EXPECT_FALSE(model_associator_->local_tab_pool_.Empty());
  // Total capacity = 1 Associated Node + kFreeNodesHighWatermark free node.
  EXPECT_EQ(TabNodePool::kFreeNodesHighWatermark + 1,
            model_associator_->local_tab_pool_.Capacity());

  // Freeing the last sync node should drop the free nodes to
  // kFreeNodesLowWatermark.
  model_associator_->local_tab_pool_.FreeTabNode(last_sync_id);
  EXPECT_FALSE(model_associator_->local_tab_pool_.Empty());
  EXPECT_TRUE(model_associator_->local_tab_pool_.Full());
  EXPECT_EQ(TabNodePool::kFreeNodesLowWatermark,
            model_associator_->local_tab_pool_.Capacity());
}

TEST_F(ProfileSyncServiceSessionTest, TabNodePoolDeleteUnassociatedNodes) {
  CreateRootHelper create_root(this);
  ASSERT_TRUE(StartSyncService(create_root.callback(), false));
  std::string local_tag = model_associator_->GetCurrentMachineTag();
  syncer::SyncError error;
  // Create a free node and then dissassociate sessions so that it ends up
  // unassociated.
  int tab_node_id = model_associator_->local_tab_pool_.GetFreeTabNode();
  // Update the tab_id of the node, so that it is considered a valid
  // unassociated node otherwise it will be mistaken for a corrupted node and
  // will be deleted before being added to the tab node pool.
  {
    std::string tab_tag = TabNodePool::TabIdToTag(local_tag, tab_node_id);
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    syncer::WriteNode tab_node(&trans);
    ASSERT_EQ(syncer::BaseNode::INIT_OK,
              tab_node.InitByClientTagLookup(syncer::SESSIONS, tab_tag));
    sync_pb::SessionSpecifics specifics = tab_node.GetSessionSpecifics();
    sync_pb::SessionTab* tab = specifics.mutable_tab();
    tab->set_tab_id(1);
    tab_node.SetSessionSpecifics(specifics);
  }

  error = model_associator_->DisassociateModels();
  ASSERT_FALSE(error.IsSet());
  error = model_associator_->AssociateModels(NULL, NULL);
  ASSERT_FALSE(error.IsSet());
}

}  // namespace browser_sync


text:
Home » Tournament in Focus » - English Premier League » Spurs ready to bid for former United forward
Spurs ready to bid for former United forward
Tottenham are ready to bid £50 million to sign former Manchester United forward Memphis Depay.
The Dutch international joined the Old Trafford side in 2015 from PSV Eindhoven, the same year as he was named the "Best Young Player in the World" by France Football, but had a largely forgettable spell in North-West England, where he became better known for his social life off the pitch than his performances on it.
He subsequently joined Ligue 1 side Lyon in January 2017, and has gone on to rebuild his career and reputation, scoring 50 goals in 130 appearances for them. He has also become an integral part of Ronald Koeman's Dutch side which reached the finals of the Nations Cup earlier this year.
His form has attracted scouts from leading clubs across Europe, and Tottenham, who are looking to boost their forward options, have now emerged as one of the favourites to sign him.
West Ham consider selling former Manchester United forward
SkySports report that West Ham United are ready to listen to offers for former Manchester…
Spurs ready to bid £35 million for Fulham teenage left-back
The Daily Telegraph reports that Tottenham Hotspur are ready to offer £35 million to sign…
Former Spurs star and Manchester United target linked with Bayern move
The Independent reports that Gareth Bale has not ruled out a move to Bayern Munich…
Spurs tracking young Watford forward
Watford's Brazilian forward Richardlison has only been in English football for 3 months but he…
lyon Premier League THFC tottenham hotspur	2019-11-11

redpajama_set_name: RedPajamaCommonCrawl


text:
Foodie Bogotá, Colombia
17 August 2009 by Nicholas Gill in Colombia GUIDES TRAVEL - No Comments
Increased air service from the east coast of the United States (both JetBlue and Spirit have recently begin flying there) and from within Latin America has made the Colombian capital of Santa Fe de Bogotá a surprisingly affordable and easy to reach destination. Most are pleasantly surprised at just how much there is to do in the city and the sheer number of great restaurants and street fare that can be found there.
Fine Dining Restaurants in Bogotá:
Anderson's – Southern style (that's U.S. southern style) food that uses all parts of the pig (when they have ribs on the menu, there will be house made sausage and bacon to follow) and serves a mean bananas foster.
Address: AV 6 # 10-19, La Candelaria,Tel: 2831573
Restaurant Review: Anderson's
Andrés Carne de Res: A roadside steak stand that has become a gluttonous Wonka like maze of Colombian curios where you feast on Argentine beef and chorizo and drink handles of aguardiente mixed with fresh fruit juices and then rumba in the same spot and on the tables until 5am. Soap stars, models, and politicians all flock here even though it is 40 minutes outside the center in the tiny town of Chia.
Address: Calle 3 No 11a – 56, Chia, Tel: 8637880
Restaurant Review: Andres Carne de Res
Astrid y Gastón: Latin America's biggest celebrity chef, Gastón Acurio of Lima, choose Bogotá for his third outpost of his well known contemporary Peruvian restaurant (the second was in Santiago). The restaurant is not overly adventurous, but is rivaled only by Rafael for superior quality Peruvian dishes.
Address: Carrera 7 No 67-64, Tel:2111400
Rafael: Rafael Osterling's Bogotá outpost of his Lima, Peru restaurant (another has opened in Buenos Aires) sits in the Colombian city's northern suburbs. Visually, the restaurant is stunning. With dark with bold and bright splashes of color and flowers it looks like a Renoir painting. Start with ceviche or tiradito and a pisco cocktail like their Mango biche sour. Then move on to Osterling's signature dishes such as Lomo Saltado.
Address: Calle 70 No 4-65, Tel: 571-2554138
Ginger Ceviche at 80 Sillas Bogota
80 Sillas & Central Cevicheria: These two restaurants operated by the same owners are at the center of the city's growing ceviche scene. The menu is simple: One side is meat, the other is seafood. The seafood side offers a list of ceviches with a choice of fish, mixed, octopus, shrimp, or squid in a range of styles. The combinations are seemingly endless.
80 Sillas Address: Calle 118 No. 7-09, Usaquen, Tel: 571-619-2471
Central Cevicheria Address: Cra 13 y Calle 85
Restaurant Review: 80 Sillas
Casa Vieja: For typical Colombian dishes in a typical Spanish colonial atmosphere, Casa Veija on the edge of La Candelaria (also a location in the Zona G) is a sure thing. Try their ajiaco.
Address: Av. Jiménez No. 3-63, La Candelaria, Tel: 571-334-8908
Leo Cocina y Cava: Perhaps the best known female Colombian chef, Leonor Espinoza's restaurant delves deep into Colombian culinary styles and native ingredients. Try the carimañola (yucca empanada) filled with smoked rabbit.
Address: Calle 27B Nº 6-75, Tel. 571-283-8659
Donostia:Chef Tomás Rueda studied cooking in Spain, yet was born in Colombia, therefore his cuisine tends to be a fusion of the two. Spanish tapas and a menu that frequently uses Serrano ham and Manchego cheese is paired with contemporary Colombian dishes such as Crema de Papas con Morcilla (Cream of Potato Soup with Blood Sausage).
Address: Calle 29 bis Nº 5-89, Tel. 571-287-3943
Traditional Dishes & Street Food in Bogotá:
Arepas: These are the quintessential Colombian snack. These corn pancakes are sold form small carts all over the city, sometimes stuffed with ham, cheese, or eggs. Sometimes they are grilled alongside chorizo.
Mazorca: Basically just corn grilled over charcoal grills and served with melted butter and salt. Keep an eye out in La Candelaria.
Fruit: Many first time visitors don't realize just how great the diversity of fruit really is in Colombia until they walk a city street and find dozens of fruit carts in a single neighborhood selling brightly colored, sweet smelling fruits. Try the Mango Biche, a tart green mango served with salt, pepper and lemon juice.
Coffee: While good coffee can be found right in the street from roving vendors, the Colombian version of Starbuck's are the Juan Valdez Cafes which are splashed out across the city.
Santafereño: For many Bogotanos their first meal of the day is a cup of hot chocolate served with a slab of cheese that is dropped in the chocolate to melt. Find at most cafes and small shops throughout the center, as well as at roving carts.
Morcilla & Fritanga: Better known as Blood Sausage, this local staple is more for than the adventurous eater, though many refined palates often head to Fritanga Doña Segunda (Calle 73 & Carrera 39, Barrios Unidos, Tel. 571-631-1423) to get their fill as well.
Sancocho de Gallina: This hearty chicken stew is served at hundreds of rustic eateries around the capital, but nowhere as famously as at Doña Elvira (Calle 50 No. 20-28, Chapinero, Tel. 571-235-8275, www.restaurantedonaelvira.com).
What to do in Bogota between Meals
Museums:
Museo de Oro: The most elaborate collection of Pre-Colombian gold objects in the world sits in a modern setting in the center of the city.
Museo Botero: The works of Colombian painter Fernando Botero (he created the chubby Mona Lisa) are the focus of this fine musuem in La Candelaria.
Visit Las Pulgas, the Sunday flea market in Usaquen, an upscale/bohemian district where you can wander in and out of restaurants, bars and cafés while picking up the odd souvenier.
Interesting Day Trip:
The Salt Cathedral: An underground cathedral carved out of salt mines just outside the city where high cultural activities like operas and theater are held. It's in the quaint town of Zipaquira, less than an hour from the center of Bogotá.
104 Art Suites: A beautiful and original design hotel that isn't overpriced. Each of the suites is designed by a local artists and every suite has two spacious rooms. In 2009, they added a smaller sister property a few kilometers away in Usaquen.
Casa Medina: A trendy, 58-room boutique hotel in a great location and superb amenities and service.
Hotel La Opera: This restored colonial era Republican mansion is the best hotel in town and found right in the heart of La Candelaria.
Casa Platypus: Though my stay didn't go as planned, I still recommend this new B&B down the street from the legendary hostel.
–A Foodie's Guide to Cuzco, Peru
–A Foodie's Guide to Iquitos, Peru
–The Art of the Chilean Sandwich
Nicholas Gill (470 Posts)
Writer and photographer Nicholas Gill is the editor/publisher of New World Review. He lives in Lima, Peru and Brooklyn, New York. His work has appeared in publications such as the New York Times, Los Angeles Times, CondeNast Traveler, National Geographic Traveler, Afar, and Penthouse. Visit his personal website (nicholas-gill.com) for more information.
Belgian Fries
AROUND THE WEB: August 24
9 Peruvian Fruits You Need To Know
Airlines and Bus Routes to La Paz, Bolivia
Pisco, Peru's Drink From the Desert – washingtonpost.com
Machaca'o
Noma Co-founder to Open Gustu in La Paz, Bolivia in April
Foodie Cuzco, Peru
Airlines That Fly To & Within Central America
Dawn on the Amazon Captain's Blog
Where And Back Again


text:
Bernard Bajolet (born 21 May 1949) is a French diplomat and civil servant. On 10 April 2013 he was appointed as head of the French secret service, the Directorate-General for External Security (Direction générale des services extérieurs). He was the French Ambassador to Afghanistan from 2011 to 2013.

Early life
Bajolet was born in 1949 at Dombasle-sur-Meurthe and went to Lycée Henri Poincaré in Nancy, France.  He then studied at Paris' Sciences Po, and graduated from the École nationale d'administration in 1975.  That same year he joined the diplomatic service.

On October 18, 2022, Bernard Bajolet is indicted for "complicity in attempted extortion" and "arbitrary attack on individual freedom by a person holding public authority", against Alain Dumenil, a Franco-Swiss businessman who accuses the intelligence service of having used coercion to demand money from him in 2016..

Diplomatic career

1975-1978: Deuxième Secrétaire and then Premier Secrétaire at Algiers
1978-1979: at the Administration centrale (Information and Press for European issues)
1979-1981: au Cabinet du Secrétaire d'Etat aux Affaires Etrangères, responsible for relations with the European Parliament
1981-1985: deuxième Conseiller at Rome
1985-1986: at Harvard University
1986-1990: deuxième Conseiller at Damascus
1991-1994: at the Administration Centrale, (Directeur Adjoint for North Africa and the Middle East)
1991: Special envoy to the Madrid Conference of 1991
1994-1998: French ambassador to Jordan
1999-2003: Ambassador to Bosnia and Herzegovina
Sept. 2003-Aug. 2006: Chief of the section of French interests and then Ambassador to Iraq
Nov. 2006-2008: Ambassador and High Representative to Algeria
July 2008-Feb. 2011: coordinator of intelligence for President Nicolas Sarkozy as head of the Conseil national du renseignement
2011-2013: Ambassador to Afghanistan

References

1949 births
Living people
Harvard University people
École nationale d'administration alumni
Sciences Po alumni
Ambassadors of France to Afghanistan
Ambassadors of France to Algeria
Ambassadors of France to Jordan
Ambassadors of France to Bosnia and Herzegovina
Ambassadors of France to Iraq
Officiers of the Légion d'honneur

redpajama_set_name: RedPajamaWikipedia


Apple, Inc. (AAPL) reported its latest earnings results on Tuesday, January 29. The company reported a decrease in both sales and profits.
The technology giant posted total net sales of $84.31 billion for the quarter. This is down from $88.29 billion for the same quarter last year.
"While it was disappointing to miss our revenue guidance, we manage Apple for the long term, and this quarter's results demonstrate that the underlying strength of our business runs deep and wide," said Apple's CEO, Tim Cook. "Our active installed base of devices reached an all-time high of 1.4 billion in the first quarter, growing in each of our geographic segments."
Apple reported net income of $19.96 billion for the quarter, or $4.18 per share. At this time last year, the company posted net income of $20.07 billion.
Sales of the company's iPhone devices dropped to $51.98 billion for the quarter, a nearly 15% decrease from the same quarter last year. The company's other product categories each produced an uptick in quarterly sales. Apple's Services segment, which includes iCloud and Apple Music, grew to $10.88 billion in revenue for the quarter.
Apple, Inc. (AAPL) shares ended the week at $166.52, up 6.9% for the week.
Amazon.com, Inc. (AMZN) released its fourth quarter results on Thursday, January 31. The company reported increases in both quarterly and full-year profits.
The digital retailer reported $72.38 billion in revenue for the quarter, up from $60.45 billion during the same quarter last year. For the full year, Amazon reported $232.89 billion in revenue.
"Alexa was very busy during her holiday season. Echo Dot was the best-selling item across all products on Amazon globally, and customers purchased millions more devices from the Echo family compared to last year," said Jeff Bezos, CEO and founder of Amazon. "The number of research scientists working on Alexa has more than doubled in the past year, and the results of the team's hard work are clear."
Amazon reported net income of $3.03 billion in the fourth quarter, up from $1.86 billion at this time last year. The company posted full-year net income of $10.07 billion, topping the $3.03 billion reported the previous year.
The company's North America segment led the way with $44.12 billion in net sales during the quarter and $141.37 billion during the full year. International net sales reached $20.83 billion for the quarter and $65.86 billion for the year. Amazon Web Services (AWS) posted $7.43 billion in quarterly sales and $25.66 billion for the year.
Amazon (AMZN) shares ended the week at $1,626.23, down 1.1% for the week.
Tesla, Inc. (TSLA) posted its latest earnings report on Wednesday, January 30. The company posted declining profits despite increased revenue.
The automaker reported revenue of $7.23 billion for the quarter, up from $6.82 billion during the same quarter last year. For the full year, the company reported $21.46 billion in revenue, up from $11.76 billion during the previous year.
"Last year was the most pivotal year in Tesla's history," wrote company CEO Elon Musk and CFO Deepak Ahuja in a letter to shareholders. "During our Model 3 production ramp, we went through significant challenges with the battery module line at Gigafactory 1 in Nevada, and later with our general assembly line in Fremont. Thanks to the hard work and ingenuity of our manufacturing teams, by mid-2018 we successfully overcame these challenges and stabilized Model 3 production at high volumes."
The company reported net income of $139.5 million for the quarter. This is down from net income of $311.5 million at this time last year.
Tesla's shares fell roughly 5% following the earnings release. During the earnings webcast, the company announced that its CEO, Deepak Ahuja, would be retiring from the company later this year. Ahuja initially retired from the company in 2015, but returned in 2017 after the departure of CEO Jason Wheeler.
Tesla (TSLA) shares ended the week at $312.21, up 6.6% for the week.
The Dow started the week of 1/28 at 24,597 and closed at 25,064 on 2/1. The S&P 500 started the week at 2,645 and closed at 2,707. The NASDAQ started the week at 7,075 and closed at 7,264.
As the week came to an end, U.S. Treasury yields rose in response to a jobs report that outpaced expectations. The rise in yields reversed the previous day's decline.
On Friday, the Bureau of Labor Statistics released its monthly Employment Situation Summary. The report showed nonfarm payroll employment rose by 304,000 in January. Analysts expected an increase of only 165,000 jobs for the month. The unemployment rate ticked upward 0.1% to 4%.
"The labor force was little changed but the drop in employment was enough to lift the participation rate by a tenth to a new cycle high," said Ian Shepherdson, Chief Economist with Pantheon Macroeconomics. "But the increase in participation in recent months is not enough yet to call a meaningful shift from the flat trend of the past few years, and we still expect unemployment to fall over the course of this year."
The benchmark 10-year Treasury note was at 2.688% during trading on Friday after falling to a low of 2.613% on Thursday. Likewise, the 30-year Treasury bond was at 3.031% on Friday after bottoming out at 2.989% the day prior.
Thursday's dip in yields followed a statement released on Wednesday by the Federal Open Market Committee which indicated a slow-down in further rate hikes. In its statement, the Fed announced that the target federal funds rate would remain at a range of 2.25% — 2.5%.
"The Committee continues to view sustained expansion of economic activity, strong labor market conditions, and inflation near the Committee's symmetric 2% objective as the most likely outcomes," noted the Federal Reserve in Wednesday's statement. "In light of global economic and financial developments and muted inflation pressures, the Committee will be patient as it determines what future adjustments to the target range for the federal funds rate may be appropriate to support these outcomes."
The 10-year Treasury note yield closed at 2.69%, while the 30-year Treasury bond yield was 3.03%.
Freddie Mac released the results of its latest Primary Mortgage Market Survey on Thursday, January 31. The report showed a slight increase in interest rates.
This week, the 30-year fixed rate mortgage averaged 4.46%, up from 4.45% at this time last week. Last year at this time, the 30-year fixed rate mortgage averaged 4.22%.
The 15-year fixed rate mortgage averaged 3.89% this week, up from last week's average of 3.88%. During the same period last year, the 15-year fixed rate mortgage averaged 3.68%.
"Purchase applications were down this week after soaring early in the year," said Freddie Mac's Chief Economist, Sam Khater. "However, softening house price appreciation along with increasing inventory of homes on the market — and historically low mortgage rates — should give a boost to the spring homebuying season."
Based on published national averages, the money market account closed at 1.29% on 2/1. The one-year CD finished at 2.76%.


text:
West Hampshire CCG
The website of the NHS West Hampshire Clinical Commissioning Group (CCG)
Results for {phrase}
More results ...
Our plans (strategies and annual reports)
Our documents library (policies and meeting papers)
Our corporate meetings held in public
Safeguarding commitments
Health Involvement Network
Consultations and surveys
Service planning
Patient and Public Engagement Steering Group
Get involved with your GP practice
Healthwatch Hampshire
Social media acceptable use policy
NHS Continuing Healthcare during COVID-19
NHS-funded Nursing Care
Contacting the NHS CHC Service
Provide informal feedback
Make a formal complaint
Submit a Freedom of Information Request
Submit a subject access request (Individual rights requests)
Continuing Healthcare or Funded Nurse Care
You are here: Home / News / Hampshire Together programme launched to plan new hospital and future services
Hampshire Together programme launched to plan new hospital and future services
HEALTH leaders from across the county are united in their support of the 'Hampshire Together: Modernising our Hospitals and Health Services' programme. Hampshire Together, which was launched last week, involves NHS and social care providers across Alton, Andover, Basingstoke, Eastleigh, Winchester and the surrounding areas.
The centrepiece will be the construction of a brand new hospital – but the programme's impact will reach far beyond the walls of this new building, involving GPs, mental health, community care, social care and the wider voluntary sector, as well as acute hospital care.
Dr Nicola Decker, North Hampshire Clinical Chair for the Hampshire and Isle of Wight Partnership of Clinical Commissioning Groups (one of the organisations leading the Hampshire Together programme in partnership with us and Hampshire Hospitals NHS Foundation Trust), said: "The central aim of Hampshire Together is to deliver a healthcare system that supports patients, their families and their carers to access the right care, in the right place, at the right time in order to keep them healthy."
Alex Whitfield, chief executive of Hampshire Hospitals, which runs Andover War Memorial Hospital, Basingstoke and North Hampshire Hospital and Royal Hampshire County Hospital, in Winchester, added: "The new hospital that this programme will deliver will be designed in such a way that it could accommodate partners in mental health, primary health, social care and more, marking Hampshire out as a leader when it comes to joining up care services across the different sectors."
Grant MacDonald, chief operating officer at Southern Health NHS Foundation Trust, which provides community health, specialist mental health and learning disability services, also supports the aims of Hampshire Together.
"Hampshire Together is a real opportunity to improve care for local people by working better together," Grant said. "We are keen to support this initiative and help ensure people using our services, local communities and staff are involved in shaping these plans."
Their thoughts are echoed by Mark Ainsworth, director of operations at South Central Ambulance Service NHS Foundation Trust, who operate emergency and non-emergency ambulance services as well as the NHS 111 service across the area.
He said: "We are really pleased to be involved in this programme and look forward to playing our part in helping people across Hampshire access the fantastic services that will be available and enabling our staff to get the right treatment to all our patients when they need it most."
As part of the Hampshire Together programme, people across north and mid Hampshire are currently being asked for their views on the way health services are provided in the area now and in the future.
https://www.hampshiretogether.nhs.uk/ All of the information you need and an online comment form can be found at www.hampshiretogether.nhs.uk. You can also find Hampshire Together on Twitter (www.twitter.com/HampshireMOHHS) and Facebook (www.facebook.com/hampshiretogether). If you would like to receive the listening document and a comment form by post, write to the programme at FREEPOST Hampshire Together (no stamp required) with your name and address.
The NHS urges people with liver conditions to get their flu vaccine
NHS COVID-19 Vaccination Programme and Roll-Out – Hampshire and Isle of Wight update
COVID-19 Vaccination Centres to launch across Hampshire and Isle of Wight
NHS 111 First: bigger, better, and around-the-clock
The NHS urges people with a high Body Mass Index (BMI) to get their flu vaccine
Your local NHS is here for you this festive season
COVID-19 vaccination scam alert
Further GP-led services gearing up to offer Covid vaccine to patients
More GP surgeries gearing up to offer Covid vaccine to patients
Document Store
GP Portal
Medicines Optimisation
RESTORE2™ official
NHS e-Referrals: Mental Health
Share your compliments, comments and complaints
NHS West Hampshire Clinical Commissioning Group
Omega House
110 Southampton Road
SO50 5PB
Copyright © 2021 West Hampshire CCG | Powered by HealthWeb Solutions | Dashboard | Log in