# frozen_string_literal: true

describe DiscourseConnect do
  before do
    @discourse_connect_url = "http://example.com/discourse_sso"
    @discourse_connect_secret = "shjkfdhsfkjh"

    SiteSetting.discourse_connect_url = @discourse_connect_url
    SiteSetting.enable_discourse_connect = true
    SiteSetting.discourse_connect_secret = @discourse_connect_secret
    SiteSetting.reserved_usernames = ''
    Jobs.run_immediately!
  end

  def make_sso
    sso = DiscourseConnectBase.new
    sso.sso_url = "http://meta.discorse.org/topics/111"
    sso.sso_secret = "supersecret"
    sso.nonce = "testing"
    sso.email = "some@email.com"
    sso.username = "sam"
    sso.name = "sam saffron"
    sso.external_id = "100"
    sso.avatar_url = "https://cdn.discourse.org/user_avatar.png"
    sso.avatar_force_update = false
    sso.bio = "about"
    sso.admin = false
    sso.moderator = false
    sso.suppress_welcome_message = false
    sso.require_activation = false
    sso.title = "user title"
    sso.custom_fields["a"] = "Aa"
    sso.custom_fields["b.b"] = "B.b"
    sso.website = "https://www.discourse.org/"
    sso.location = "Home"
    sso
  end

  def new_discourse_sso
    DiscourseConnect.new(secure_session: secure_session)
  end

  def test_parsed(parsed, sso)
    expect(parsed.nonce).to eq sso.nonce
    expect(parsed.email).to eq sso.email
    expect(parsed.username).to eq sso.username
    expect(parsed.name).to eq sso.name
    expect(parsed.external_id).to eq sso.external_id
    expect(parsed.avatar_url).to eq sso.avatar_url
    expect(parsed.avatar_force_update).to eq sso.avatar_force_update
    expect(parsed.bio).to eq sso.bio
    expect(parsed.admin).to eq sso.admin
    expect(parsed.moderator).to eq sso.moderator
    expect(parsed.suppress_welcome_message).to eq sso.suppress_welcome_message
    expect(parsed.require_activation).to eq false
    expect(parsed.title).to eq sso.title
    expect(parsed.custom_fields["a"]).to eq "Aa"
    expect(parsed.custom_fields["b.b"]).to eq "B.b"
    expect(parsed.website).to eq sso.website
    expect(parsed.location).to eq sso.location
  end

  it "can do round trip parsing correctly" do
    sso = DiscourseConnectBase.new
    sso.sso_secret = "test"
    sso.name = "sam saffron"
    sso.username = "sam"
    sso.email = "sam@sam.com"

    sso = DiscourseConnectBase.parse(sso.payload, "test")

    expect(sso.name).to eq "sam saffron"
    expect(sso.username).to eq "sam"
    expect(sso.email).to eq "sam@sam.com"
  end

  let(:ip_address) { "127.0.0.1" }
  let(:secure_session) { SecureSession.new("abc") }

  it "bans bad external id" do
    sso = new_discourse_sso
    sso.username = "test"
    sso.name = ""
    sso.email = "test@test.com"
    sso.suppress_welcome_message = true

    sso.external_id = "    "

    expect do
      sso.lookup_or_create_user(ip_address)
    end.to raise_error(DiscourseConnect::BlankExternalId)

    sso.external_id = nil

    expect do
      sso.lookup_or_create_user(ip_address)
    end.to raise_error(DiscourseConnect::BlankExternalId)

    # going for slight duplication here so our intent is crystal clear
    %w{none nil Blank null}.each do |word|
      sso.external_id = word
      expect do
        sso.lookup_or_create_user(ip_address)
      end.to raise_error(DiscourseConnect::BannedExternalId)
    end
  end

  it "can lookup or create user when name is blank" do
    sso = new_discourse_sso
    sso.username = "test"
    sso.name = ""
    sso.email = "test@test.com"
    sso.external_id = "A"
    sso.suppress_welcome_message = true
    user = sso.lookup_or_create_user(ip_address)

    expect(user.persisted?).to eq(true)
  end

  it "unstaged users" do
    SiteSetting.auth_overrides_name = true

    email = "staged@user.com"
    Fabricate(:user, staged: true, email: email)

    sso = new_discourse_sso
    sso.username = "staged"
    sso.name = "Bob O'Bob"
    sso.email = email
    sso.external_id = "B"
    user = sso.lookup_or_create_user(ip_address)

    user.reload

    expect(user).to_not be_nil
    expect(user.staged).to be(false)

    expect(user.name).to eq("Bob O'Bob")
  end

  context "reviewables" do
    let(:sso) do
      new_discourse_sso.tap do |sso|
        sso.username = "staged"
        sso.name = "Bob O'Bob"
        sso.email = "bob@obob.com"
        sso.external_id = "B"
      end
    end

    it "doesn't create reviewables if we aren't approving users" do
      user = sso.lookup_or_create_user(ip_address)
      reviewable = ReviewableUser.find_by(target: user)
      expect(reviewable).to be_blank
    end

    it "creates reviewables if needed" do
      SiteSetting.must_approve_users = true
      user = sso.lookup_or_create_user(ip_address)
      reviewable = ReviewableUser.find_by(target: user)
      expect(reviewable).to be_present
      expect(reviewable).to be_pending
    end
  end

  it "can set admin and moderator" do
    admin_group = Group[:admins]
    mod_group = Group[:moderators]
    staff_group = Group[:staff]

    sso = new_discourse_sso
    sso.username = "misteradmin"
    sso.name = "Bob Admin"
    sso.email = "admin@admin.com"
    sso.external_id = "id"
    sso.admin = true
    sso.moderator = true
    sso.suppress_welcome_message = true

    user = sso.lookup_or_create_user(ip_address)
    staff_group.reload

    expect(mod_group.users.where('users.id = ?', user.id).exists?).to eq(true)
    expect(staff_group.users.where('users.id = ?', user.id).exists?).to eq(true)
    expect(admin_group.users.where('users.id = ?', user.id).exists?).to eq(true)
  end

  it "can force a list of groups with the groups attribute" do
    user = Fabricate(:user)
    group1 = Fabricate(:group, name: 'group1')
    group2 = Fabricate(:group, name: 'group2')

    sso = new_discourse_sso
    sso.username = "bobsky"
    sso.name = "Bob"
    sso.email = user.email
    sso.external_id = "A"

    sso.groups = "#{group2.name.capitalize},group4,badname,trust_level_4"
    sso.lookup_or_create_user(ip_address)

    SiteSetting.discourse_connect_overrides_groups = true

    group1.reload
    expect(group1.usernames).to eq("")
    expect(group2.usernames).to eq("")

    group1.add(user)
    group1.save

    sso.lookup_or_create_user(ip_address)
    expect(group1.usernames).to eq("")
    expect(group2.usernames).to eq(user.username)

    sso.groups = "badname,trust_level_4"
    sso.lookup_or_create_user(ip_address)
    expect(group1.usernames).to eq("")
    expect(group2.usernames).to eq("")
  end

  it "can specify groups" do

    user = Fabricate(:user)

    add_group1 = Fabricate(:group, name: 'group1')
    add_group2 = Fabricate(:group, name: 'group2')
    existing_group = Fabricate(:group, name: 'group3')
    add_group4 = Fabricate(:group, name: 'GROUP4')
    existing_group2 = Fabricate(:group, name: 'GRoup5')

    [existing_group, existing_group2].each do |g|
      g.add(user)
      g.save!
    end

    add_group1.add(user)
    existing_group.save!

    sso = new_discourse_sso
    sso.username = "bobsky"
    sso.name = "Bob"
    sso.email = user.email
    sso.external_id = "A"

    sso.add_groups = "#{add_group1.name},#{add_group2.name.capitalize},group4,badname"
    sso.remove_groups = "#{existing_group.name},#{existing_group2.name.downcase},badname"

    sso.lookup_or_create_user(ip_address)

    existing_group.reload
    expect(existing_group.usernames).to eq("")

    existing_group2.reload
    expect(existing_group2.usernames).to eq("")

    add_group1.reload
    expect(add_group1.usernames).to eq(user.username)

    add_group2.reload
    expect(add_group2.usernames).to eq(user.username)

    add_group4.reload
    expect(add_group4.usernames).to eq(user.username)
  end

  it 'behaves properly when auth_overrides_username is set but username is missing or blank' do
    SiteSetting.auth_overrides_username = true

    sso = new_discourse_sso
    sso.username = "testuser"
    sso.name = "test user"
    sso.email = "test@test.com"
    sso.external_id = "100"
    sso.bio = "This **is** the bio"
    sso.suppress_welcome_message = true

    # create the original user
    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq "testuser"

    # remove username from payload
    sso.username = nil
    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq "testuser"

    # set username in payload to blank
    sso.username = ''
    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq "testuser"
  end

  it "can override name / email / username" do
    admin = Fabricate(:admin)

    SiteSetting.email_editable = false
    SiteSetting.auth_overrides_name = true
    SiteSetting.auth_overrides_email = true
    SiteSetting.auth_overrides_username = true

    sso = new_discourse_sso
    sso.username = "bob%the$admin"
    sso.name = "Bob Admin"
    sso.email = admin.email
    sso.external_id = "A"

    sso.lookup_or_create_user(ip_address)

    admin.reload

    expect(admin.name).to eq "Bob Admin"
    expect(admin.username).to eq "bob_the_admin"
    expect(admin.email).to eq admin.email

    sso.email = "TEST@bob.com"

    sso.name = "Louis C.K."

    sso.lookup_or_create_user(ip_address)

    admin.reload

    expect(admin.email).to eq("test@bob.com")
    expect(admin.username).to eq "bob_the_admin"
    expect(admin.name).to eq "Louis C.K."
  end

  it 'can override username properly when only the case changes' do
    SiteSetting.auth_overrides_username = true

    sso = new_discourse_sso
    sso.username = "testuser"
    sso.name = "test user"
    sso.email = "test@test.com"
    sso.external_id = "100"
    sso.bio = "This **is** the bio"
    sso.suppress_welcome_message = true

    # create the original user
    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq "testuser"

    # change the username case
    sso.username = "TestUser"
    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq "TestUser"
  end

  it 'do not override username when a new username after fixing is the same' do
    SiteSetting.auth_overrides_username = true

    sso = new_discourse_sso
    sso.username = "testuser"
    sso.name = "test user"
    sso.email = "test@test.com"
    sso.external_id = "100"

    # create the original user
    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq "testuser"

    # change the username case
    sso.username = "testuserგამარჯობა"
    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq "testuser"
  end

  it 'should preserve username when several users login with the same username' do
    SiteSetting.auth_overrides_username = true

    # if several users have username "bill" on the external site,
    # they will have usernames bill, bill1, bill2 etc in Discourse:
    Fabricate(:user, username: "bill")
    Fabricate(:user, username: "bill1")
    Fabricate(:user, username: "bill2")
    Fabricate(:user, username: "bill4")

    # the number should be preserved during subsequent logins
    # bill3 should remain bill3
    sso = new_discourse_sso
    sso.username = "bill3"
    sso.email = "test@test.com"
    sso.external_id = "100"
    sso.lookup_or_create_user(ip_address)

    sso.username = "bill"
    user = sso.lookup_or_create_user(ip_address)

    expect(user.username).to eq "bill3"
  end

  it "uses name if it's present in payload" do
    sso = new_discourse_sso
    sso.external_id = "100"

    name = "John"
    sso.name = name
    sso.email = "mail@mail.com"
    user = sso.lookup_or_create_user(ip_address)

    expect(user.name).to eq name
  end

  it "uses username for name suggestions if name isn't present in payload" do
    sso = new_discourse_sso
    sso.external_id = "100"

    sso.name = ""
    sso.username = "user_john"
    sso.email = "mail@mail.com"
    user = sso.lookup_or_create_user(ip_address)

    expect(user.name).to eq "User John"
  end

  it "uses username for username suggestions if it's present in payload" do
    sso = new_discourse_sso
    sso.external_id = "100"

    username = "user_john"
    sso.username = username
    sso.email = "mail@mail.com"
    user = sso.lookup_or_create_user(ip_address)

    expect(user.username).to eq username
  end

  it "uses name for username suggestions if username isn't present in payload" do
    sso = new_discourse_sso
    sso.external_id = "100"

    sso.username = ""
    sso.name = "John Smith"
    sso.email = "mail@mail.com"
    user = sso.lookup_or_create_user(ip_address)

    expect(user.username).to eq "John_Smith"
  end

  it "uses name for username suggestions if username consists entirely of disallowed characters" do
    SiteSetting.unicode_usernames = false

    sso = new_discourse_sso
    sso.external_id = "100"

    sso.username = "Πλάτων"
    sso.name = "Plato"
    sso.email = "mail@mail.com"
    user = sso.lookup_or_create_user(ip_address)

    expect(user.username).to eq sso.name
  end

  it "doesn't use email as a source for username suggestions by default" do
    sso = new_discourse_sso
    sso.external_id = "100"

    # set username and name to nil, so they cannot be used as a source for suggestions
    sso.username = nil
    sso.name = nil
    sso.email = "mail@mail.com"

    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq I18n.t('fallback_username')
  end

  it "uses email as a source for username suggestions if enabled" do
    SiteSetting.use_email_for_username_and_name_suggestions = true
    sso = new_discourse_sso
    sso.external_id = "100"

    # set username and name to nil, so they cannot be used as a source for suggestions
    sso.username = nil
    sso.name = nil
    sso.email = "mail@mail.com"

    user = sso.lookup_or_create_user(ip_address)
    expect(user.username).to eq "mail"
  end

  it "doesn't use email as a source for name suggestions by default" do
    sso = new_discourse_sso
    sso.external_id = "100"

    # set username and name to nil, so they cannot be used as a source for suggestions
    sso.username = nil
    sso.name = nil
    sso.email = "mail@mail.com"

    user = sso.lookup_or_create_user(ip_address)
    expect(user.name).to eq ""
  end

  it "uses email as a source for name suggestions if enabled" do
    SiteSetting.use_email_for_username_and_name_suggestions = true
    sso = new_discourse_sso
    sso.external_id = "100"

    # set username and name to nil, so they cannot be used as a source for suggestions
    sso.username = nil
    sso.name = nil
    sso.email = "mail@mail.com"

    user = sso.lookup_or_create_user(ip_address)
    expect(user.name).to eq "Mail"
  end

  it "uses email for username suggestions if username and name consist entirely of disallowed characters" do
    SiteSetting.use_email_for_username_and_name_suggestions = true
    SiteSetting.unicode_usernames = false

    sso = new_discourse_sso
    sso.external_id = "100"

    sso.username = "Πλάτων"
    sso.name = "Πλάτων"
    sso.email = "mail@mail.com"
    user = sso.lookup_or_create_user(ip_address)

    expect(user.username).to eq "mail"
  end

  it "can override username with a number at the end to a simpler username without a number" do
    SiteSetting.auth_overrides_username = true

    user = Fabricate(:user)
    sso = new_discourse_sso
    sso.external_id = "A"
    sso.email = user.email

    username_with_number = "bob1"
    username_without_number = "bob"

    sso.username = username_with_number
    sso.lookup_or_create_user(ip_address)
    user.reload
    expect(user.username).to eq username_with_number

    sso.username = username_without_number
    sso.lookup_or_create_user(ip_address)
    user.reload
    expect(user.username).to eq username_without_number
  end

  it "can override username after min_username_length was made smaller" do
    SiteSetting.auth_overrides_username = true

    user = Fabricate(:user)
    sso = new_discourse_sso
    sso.external_id = "A"
    sso.email = user.email

    long_username = "bob"
    short_username = "bo"

    SiteSetting.min_username_length = 3
    sso.username = long_username
    sso.lookup_or_create_user(ip_address)
    user.reload
    expect(user.username).to eq long_username

    SiteSetting.min_username_length = 2
    sso.username = short_username
    sso.lookup_or_create_user(ip_address)
    user.reload
    expect(user.username).to eq short_username
  end

  it "can fill in data on way back" do
    sso = make_sso

    url, payload = sso.to_url.split("?")
    expect(url).to eq sso.sso_url
    parsed = DiscourseConnectBase.parse(payload, "supersecret")

    test_parsed(parsed, sso)
  end

  it "handles sso_url with query params" do
    sso = make_sso
    sso.sso_url = "http://tcdev7.wpengine.com/?action=showlogin"

    expect(sso.to_url.split('?').size).to eq 2

    url, payload = sso.to_url.split("?")
    expect(url).to eq "http://tcdev7.wpengine.com/"
    parsed = DiscourseConnectBase.parse(payload, "supersecret")

    test_parsed(parsed, sso)
  end

  it "validates nonce" do
    _ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")

    sso = DiscourseConnect.parse(payload, secure_session: secure_session)
    expect(sso.nonce_valid?).to eq true

    other_session_sso = DiscourseConnect.parse(payload, secure_session: SecureSession.new("differentsession"))
    expect(other_session_sso.nonce_valid?).to eq false

    sso.expire_nonce!

    expect(sso.nonce_valid?).to eq false
  end

  it "allows disabling CSRF protection" do
    SiteSetting.discourse_connect_csrf_protection = false
    _ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")

    sso = DiscourseConnect.parse(payload, secure_session: secure_session)
    expect(sso.nonce_valid?).to eq true

    other_session_sso = DiscourseConnect.parse(payload, secure_session: SecureSession.new("differentsession"))
    expect(other_session_sso.nonce_valid?).to eq true

    sso.expire_nonce!

    expect(sso.nonce_valid?).to eq false
  end

  it "generates a correct sso url" do
    url, payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")
    expect(url).to eq @discourse_connect_url

    sso = DiscourseConnect.parse(payload, secure_session: secure_session)
    expect(sso.nonce).to_not be_nil
  end

  context 'nonce error' do
    it "generates correct error message when nonce has already been used" do
      _ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")

      sso = DiscourseConnect.parse(payload, secure_session: secure_session)
      expect(sso.nonce_valid?).to eq true

      sso.expire_nonce!
      expect(sso.nonce_error).to eq("Nonce has already been used")
    end

    it "generates correct error message when nonce is expired" do
      _ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")

      sso = DiscourseConnect.parse(payload, secure_session: secure_session)
      expect(sso.nonce_valid?).to eq true

      Discourse.cache.delete(sso.used_nonce_key)
      expect(sso.nonce_error).to eq("Nonce is incorrect, was generated in a different browser session, or has expired")
    end

    it "generates correct error message when nonce is expired, and csrf protection disabled" do
      SiteSetting.discourse_connect_csrf_protection = false
      _ , payload = DiscourseConnect.generate_url(secure_session: secure_session).split("?")

      sso = DiscourseConnect.parse(payload, secure_session: secure_session)
      expect(sso.nonce_valid?).to eq true

      Discourse.cache.delete(sso.used_nonce_key)
      expect(sso.nonce_error).to eq("Nonce is incorrect, or has expired")
    end
  end

  context 'user locale' do
    it 'sets default user locale if specified' do
      SiteSetting.allow_user_locale = true

      sso = new_discourse_sso
      sso.username = "test"
      sso.name = "test"
      sso.email = "test@test.com"
      sso.external_id = "123"
      sso.locale = "es"

      user = sso.lookup_or_create_user(ip_address)

      expect(user.locale).to eq("es")

      user.update_column(:locale, "he")

      user = sso.lookup_or_create_user(ip_address)
      expect(user.locale).to eq("he")

      sso.locale_force_update = true
      user = sso.lookup_or_create_user(ip_address)
      expect(user.locale).to eq("es")

      sso.locale = "fake"
      user = sso.lookup_or_create_user(ip_address)
      expect(user.locale).to eq("es")
    end
  end

  context 'trusting emails' do
    let(:sso) do
      sso = new_discourse_sso
      sso.username = "test"
      sso.name = "test"
      sso.email = "test@example.com"
      sso.external_id = "A"
      sso.suppress_welcome_message = true
      sso
    end

    it 'activates users by default' do
      user = sso.lookup_or_create_user(ip_address)
      expect(user.active).to eq(true)
    end

    it 'does not activate user when asked not to' do
      sso.require_activation = true
      user = sso.lookup_or_create_user(ip_address)
      expect(user.active).to eq(false)

      user.activate

      sso.external_id = "B"

      expect do
        sso.lookup_or_create_user(ip_address)
      end.to raise_error(ActiveRecord::RecordInvalid)

    end

    it 'does not deactivate user if email provided is capitalized' do
      SiteSetting.email_editable = false
      SiteSetting.auth_overrides_email = true
      sso.require_activation = true

      user = sso.lookup_or_create_user(ip_address)
      expect(user.active).to eq(false)

      user.update_columns(active: true)
      user = sso.lookup_or_create_user(ip_address)
      expect(user.active).to eq(true)

      sso.email = "Test@example.com"
      user = sso.lookup_or_create_user(ip_address)
      expect(user.active).to eq(true)
    end

    it 'deactivates accounts that have updated email address' do

      SiteSetting.email_editable = false
      SiteSetting.auth_overrides_email = true
      sso.require_activation = true

      user = sso.lookup_or_create_user(ip_address)
      expect(user.active).to eq(false)

      old_email = user.email

      user.update_columns(active: true)
      user = sso.lookup_or_create_user(ip_address)
      expect(user.active).to eq(true)

      user.primary_email.update_columns(email: 'xXx@themovie.com')

      user = sso.lookup_or_create_user(ip_address)
      expect(user.email).to eq(old_email)
      expect(user.active).to eq(false)

    end

  end

  context 'welcome emails' do
    let(:sso) {
      sso = new_discourse_sso
      sso.username = "test"
      sso.name = "test"
      sso.email = "test@example.com"
      sso.external_id = "A"
      sso
    }

    it "sends a welcome email by default" do
      User.any_instance.expects(:enqueue_welcome_message).once
      _user = sso.lookup_or_create_user(ip_address)
    end

    it "suppresses the welcome email when asked to" do
      User.any_instance.expects(:enqueue_welcome_message).never
      sso.suppress_welcome_message = true
      _user = sso.lookup_or_create_user(ip_address)
    end
  end

  context 'setting title for a user' do
    let(:sso) {
      sso = new_discourse_sso
      sso.username = 'test'
      sso.name = 'test'
      sso.email = 'test@test.com'
      sso.external_id = '100'
      sso.title = "The User's Title"
      sso
    }

    it 'sets title correctly' do
      user = sso.lookup_or_create_user(ip_address)
      expect(user.title).to eq(sso.title)

      sso.title = "farmer"
      user = sso.lookup_or_create_user(ip_address)

      expect(user.title).to eq("farmer")

      sso.title = nil
      user = sso.lookup_or_create_user(ip_address)

      expect(user.title).to eq("farmer")
    end
  end

  context 'setting bio for a user' do
    let(:sso) do
      sso = new_discourse_sso
      sso.username = "test"
      sso.name = "test"
      sso.email = "test@test.com"
      sso.external_id = "100"
      sso.bio = "This **is** the bio"
      sso.suppress_welcome_message = true
      sso
    end

    it 'can set bio if supplied on new users or users with empty bio' do
      # new account
      user = sso.lookup_or_create_user(ip_address)
      expect(user.user_profile.bio_cooked).to match_html("<p>This <strong>is</strong> the bio</p>")

      # no override by default
      sso.bio = "new profile"
      user = sso.lookup_or_create_user(ip_address)

      expect(user.user_profile.bio_cooked).to match_html("<p>This <strong>is</strong> the bio</p>")

      # yes override for blank
      user.user_profile.update!(bio_raw: '')

      user = sso.lookup_or_create_user(ip_address)
      expect(user.user_profile.bio_cooked).to match_html("<p>new profile</p>")

      # yes override if site setting
      sso.bio = "new profile 2"
      SiteSetting.discourse_connect_overrides_bio = true

      user = sso.lookup_or_create_user(ip_address)
      expect(user.user_profile.bio_cooked).to match_html("<p>new profile 2</p")
    end

  end

  context 'when discourse_connect_overrides_avatar is not enabled' do

    it "correctly handles provided avatar_urls" do
      sso = new_discourse_sso
      sso.external_id = 666
      sso.email = "sam@sam.com"
      sso.name = "sam"
      sso.username = "sam"
      sso.avatar_url = "http://awesome.com/image.png"
      sso.suppress_welcome_message = true

      FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"), file_from_fixtures("logo.png"))
      user = sso.lookup_or_create_user(ip_address)
      user.reload
      avatar_id = user.uploaded_avatar_id

      # initial creation ...
      expect(avatar_id).to_not eq(nil)

      # junk avatar id should be updated
      old_id = user.uploaded_avatar_id
      Upload.destroy(old_id)
      FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"), file_from_fixtures("logo.png"))
      user = sso.lookup_or_create_user(ip_address)
      user.reload
      avatar_id = user.uploaded_avatar_id

      expect(avatar_id).to_not eq(nil)
      expect(old_id).to_not eq(avatar_id)

      # FileHelper.stubs(:download) { raise "should not be called" }
      # sso.avatar_url = "https://some.new/avatar.png"
      # user = sso.lookup_or_create_user(ip_address)
      # user.reload
      #
      # # avatar updated but no override specified ...
      # expect(user.uploaded_avatar_id).to eq(avatar_id)
      #
      # sso.avatar_force_update = true
      # FileHelper.stubs(:download).returns(file_from_fixtures("logo-dev.png"))
      # user = sso.lookup_or_create_user(ip_address)
      # user.reload
      #
      # # we better have a new avatar
      # expect(user.uploaded_avatar_id).not_to eq(avatar_id)
      # expect(user.uploaded_avatar_id).not_to eq(nil)
      #
      # avatar_id = user.uploaded_avatar_id
      #
      # sso.avatar_force_update = true
      # FileHelper.stubs(:download) { raise "not found" }
      # user = sso.lookup_or_create_user(ip_address)
      # user.reload
      #
      # # we better have the same avatar
      # expect(user.uploaded_avatar_id).to eq(avatar_id)
    end

  end

  context 'when discourse_connect_overrides_avatar is enabled' do
    fab!(:sso_record) { Fabricate(:single_sign_on_record, external_avatar_url: "http://example.com/an_image.png") }

    let!(:sso) {
      sso = new_discourse_sso
      sso.username = "test"
      sso.name = "test"
      sso.email = sso_record.user.email
      sso.external_id = sso_record.external_id
      sso
    }

    let(:logo) { file_from_fixtures("logo.png") }

    before do
      SiteSetting.discourse_connect_overrides_avatar = true
    end

    it "deal with no avatar url passed for an existing user with an avatar" do
      Sidekiq::Testing.inline! do
        # Deliberately not setting avatar_url so it should not update
        sso_record.user.update_columns(uploaded_avatar_id: -1)
        user = sso.lookup_or_create_user(ip_address)
        user.reload

        expect(user).to_not be_nil
        expect(user.uploaded_avatar_id).to eq(-1)
      end
    end

    it "deal with no avatar_force_update passed as a boolean" do
      Sidekiq::Testing.inline! do
        FileHelper.stubs(:download).returns(logo)

        sso_record.user.update_columns(uploaded_avatar_id: -1)

        sso.avatar_url = "http://example.com/a_different_image.png"
        sso.avatar_force_update = false

        user = sso.lookup_or_create_user(ip_address)
        user.reload

        expect(user).to_not be_nil
        expect(user.uploaded_avatar_id).to_not eq(-1)
      end
    end
  end

  context 'when discourse_connect_overrides_profile_background is not enabled' do

    it "correctly handles provided profile_background_urls" do
      sso = new_discourse_sso
      sso.external_id = 666
      sso.email = "sam@sam.com"
      sso.name = "sam"
      sso.username = "sam"
      sso.profile_background_url = "http://awesome.com/image.png"
      sso.suppress_welcome_message = true

      FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"))
      user = sso.lookup_or_create_user(ip_address)
      user.reload
      user.user_profile.reload
      profile_background_url = user.profile_background_upload.url

      # initial creation ...
      expect(profile_background_url).to_not eq(nil)
      expect(profile_background_url).to_not eq('')

      FileHelper.stubs(:download) { raise "should not be called" }
      sso.profile_background_url = "https://some.new/avatar.png"
      user = sso.lookup_or_create_user(ip_address)
      user.reload
      user.user_profile.reload

      # profile_background updated but no override specified ...
      expect(user.profile_background_upload.url).to eq(profile_background_url)
    end
  end

  context 'when discourse_connect_overrides_profile_background is enabled' do
    fab!(:sso_record) { Fabricate(:single_sign_on_record, external_profile_background_url: "http://example.com/an_image.png") }

    let!(:sso) {
      sso = new_discourse_sso
      sso.username = "test"
      sso.name = "test"
      sso.email = sso_record.user.email
      sso.external_id = sso_record.external_id
      sso
    }

    let(:logo) { file_from_fixtures("logo.png") }

    before do
      SiteSetting.discourse_connect_overrides_profile_background = true
    end

    it "deal with no profile_background_url passed for an existing user with a profile_background" do
      # Deliberately not setting profile_background_url so it should not update
      sso_record.user.user_profile.clear_profile_background
      user = sso.lookup_or_create_user(ip_address)
      user.reload

      expect(user.profile_background_upload).to eq(nil)
    end

    it "deal with a profile_background_url passed for an existing user with a profile_background" do
      url = "http://example.com/a_different_image.png"
      stub_request(:get, url).to_return(body: logo)

      sso_record.user.user_profile.clear_profile_background
      sso.profile_background_url = "http://example.com/a_different_image.png"
      user = sso.lookup_or_create_user(ip_address)
      user.reload

      expect(user.profile_background_upload).to_not eq(nil)
    end
  end

  context 'when discourse_connect_overrides_card_background is not enabled' do

    it "correctly handles provided card_background_urls" do
      sso = new_discourse_sso
      sso.external_id = 666
      sso.email = "sam@sam.com"
      sso.name = "sam"
      sso.username = "sam"
      sso.card_background_url = "http://awesome.com/image.png"
      sso.suppress_welcome_message = true
      FileHelper.stubs(:download).returns(file_from_fixtures("logo.png"))
      user = sso.lookup_or_create_user(ip_address)
      user.reload
      user.user_profile.reload
      card_background_url = user.user_profile.card_background_upload.url

      # initial creation ...
      expect(card_background_url).to be_present

      FileHelper.stubs(:download) { raise "should not be called" }
      sso.card_background_url = "https://some.new/avatar.png"
      user = sso.lookup_or_create_user(ip_address)
      user.reload
      user.user_profile.reload

      # card_background updated but no override specified ...
      expect(user.user_profile.card_background_upload.url).to eq(
        card_background_url
      )
    end
  end

  context 'when discourse_connect_overrides_card_background is enabled' do
    fab!(:sso_record) { Fabricate(:single_sign_on_record, external_card_background_url: "http://example.com/an_image.png") }

    let!(:sso) {
      sso = new_discourse_sso
      sso.username = "test"
      sso.name = "test"
      sso.email = sso_record.user.email
      sso.external_id = sso_record.external_id
      sso
    }

    let(:logo) { file_from_fixtures("logo.png") }

    before do
      SiteSetting.discourse_connect_overrides_card_background = true
    end

    it "deal with no card_background_url passed for an existing user with a card_background" do
      # Deliberately not setting card_background_url so it should not update
      sso_record.user.user_profile.clear_card_background
      user = sso.lookup_or_create_user(ip_address)
      user.reload

      expect(user.user_profile.card_background_upload).to eq(nil)
    end

    it "deal with a card_background_url passed for an existing user with a card_background_url" do
      url = "http://example.com/a_different_image.png"
      stub_request(:get, url).to_return(body: logo)

      sso_record.user.user_profile.clear_card_background
      sso.card_background_url = url

      user = sso.lookup_or_create_user(ip_address)
      user.reload

      expect(user.user_profile.card_background_upload.url).to_not eq('')
    end
  end

  context "when user is staged" do
    it "uses username of the staged user if username is not present in payload" do
      staged_username = "staged_user"
      email = "staged@user.com"
      Fabricate(:user, staged: true, username: staged_username, email: email)

      sso = new_discourse_sso
      sso.email = email
      sso.external_id = "B"

      user = sso.lookup_or_create_user(ip_address)
      user.reload

      expect(user.username).to eq(staged_username)
    end

    it "uses username of the staged user if username in payload is the same" do
      # it's important to check this, because we had regressions
      # where usernames were changed to the same username with "1" added at the end
      staged_username = "staged_user"
      email = "staged@user.com"
      Fabricate(:user, staged: true, username: staged_username, email: email)

      sso = new_discourse_sso
      sso.username = staged_username
      sso.email = email
      sso.external_id = "B"

      user = sso.lookup_or_create_user(ip_address)
      user.reload

      expect(user.username).to eq(staged_username)
    end
  end
end