pidgin.rb 16.7 KB
Newer Older
Tails developers's avatar
Tails developers committed
1
# Extracts the secrets for the XMMP account `account_name`.
2
def xmpp_account(account_name, required_options = [])
3
  begin
Tails developers's avatar
Tails developers committed
4
    account = $config["Pidgin"]["Accounts"]["XMPP"][account_name]
5
    check_keys = ["username", "domain", "password"] + required_options
Tails developers's avatar
Tails developers committed
6
    for key in check_keys do
7
      assert(account.has_key?(key))
Tails developers's avatar
Tails developers committed
8
9
      assert_not_nil(account[key])
      assert(!account[key].empty?)
10
11
12
13
    end
  rescue NoMethodError, Test::Unit::AssertionFailedError
    raise(
<<EOF
14
Your Pidgin:Accounts:XMPP:#{account} is incorrect or missing from your local configuration file (#{LOCAL_CONFIG_FILE}). See wiki/src/contribute/release_process/test/usage.mdwn for the format.
15
16
17
EOF
)
  end
Tails developers's avatar
Tails developers committed
18
  return account
19
20
end

kytv's avatar
kytv committed
21
22
23
24
def wait_and_focus(img, time = 10, window)
  begin
    @screen.wait(img, time)
  rescue FindFailed
25
    $vm.focus_window(window)
kytv's avatar
kytv committed
26
27
28
29
    @screen.wait(img, time)
  end
end

kytv's avatar
kytv committed
30
def focus_pidgin_irc_conversation_window(account)
anonym's avatar
anonym committed
31
32
33
  account = account.sub(/^irc\./, '')
  try_for(20) do
    $vm.focus_window(".*#{Regexp.escape(account)}$")
34
  end
35
36
end

37
When /^I create my XMPP account$/ do
Tails developers's avatar
Tails developers committed
38
  account = xmpp_account("Tails_account")
39
40
41
42
  @screen.click("PidginAccountManagerAddButton.png")
  @screen.wait("PidginAddAccountWindow.png", 20)
  @screen.click_mid_right_edge("PidginAddAccountProtocolLabel.png")
  @screen.click("PidginAddAccountProtocolXMPP.png")
43
44
45
46
  # We first wait for some field that is shown for XMPP but not the
  # default (IRC) since we otherwise may decide where we click before
  # the GUI has updated after switching protocol.
  @screen.wait("PidginAddAccountXMPPDomain.png", 5)
47
  @screen.click_mid_right_edge("PidginAddAccountXMPPUsername.png")
Tails developers's avatar
Tails developers committed
48
  @screen.type(account["username"])
49
  @screen.click_mid_right_edge("PidginAddAccountXMPPDomain.png")
Tails developers's avatar
Tails developers committed
50
  @screen.type(account["domain"])
51
  @screen.click_mid_right_edge("PidginAddAccountXMPPPassword.png")
Tails developers's avatar
Tails developers committed
52
  @screen.type(account["password"])
53
  @screen.click("PidginAddAccountXMPPRememberPassword.png")
Tails developers's avatar
Tails developers committed
54
  if account["connect_server"]
55
56
    @screen.click("PidginAddAccountXMPPAdvancedTab.png")
    @screen.click_mid_right_edge("PidginAddAccountXMPPConnectServer.png")
Tails developers's avatar
Tails developers committed
57
    @screen.type(account["connect_server"])
58
  end
59
60
61
62
  @screen.click("PidginAddAccountXMPPAddButton.png")
end

Then /^Pidgin automatically enables my XMPP account$/ do
63
  $vm.focus_window('Buddy List')
anonym's avatar
anonym committed
64
  @screen.wait("PidginAvailableStatus.png", 60*3)
65
66
end

67
Given /^my XMPP friend goes online( and joins the multi-user chat)?$/ do |join_chat|
68
69
  account = xmpp_account("Friend_account", ["otr_key"])
  bot_opts = account.select { |k, v| ["connect_server"].include?(k) }
70
  if join_chat
71
    bot_opts["auto_join"] = [@chat_room_jid]
72
73
  end
  @friend_name = account["username"]
Tails developers's avatar
Tails developers committed
74
  @chatbot = ChatBot.new(account["username"] + "@" + account["domain"],
75
                         account["password"], account["otr_key"], bot_opts)
76
  @chatbot.start
77
  add_after_scenario_hook { @chatbot.stop }
78
  $vm.focus_window('Buddy List')
79
80
81
82
  @screen.wait("PidginFriendOnline.png", 60)
end

When /^I start a conversation with my friend$/ do
83
  $vm.focus_window('Buddy List')
Tails developers's avatar
Tails developers committed
84
  # Clicking the middle, bottom of this image should query our
85
86
87
88
89
90
91
  # friend, given it's the only subscribed user that's online, which
  # we assume.
  r = @screen.find("PidginFriendOnline.png")
  bottom_left = r.getBottomLeft()
  x = bottom_left.getX + r.getW/2
  y = bottom_left.getY
  @screen.doubleClick_point(x, y)
Tails developers's avatar
Tails developers committed
92
  # Since Pidgin sets the window name to the contact, we have no good
93
94
95
96
97
  # way to identify the conversation window. Let's just look for the
  # expected menu bar.
  @screen.wait("PidginConversationWindowMenuBar.png", 10)
end

98
99
100
And /^I say (.*) to my friend( in the multi-user chat)?$/ do |msg, multi_chat|
  msg = "ping" if msg == "something"
  msg = msg + Sikuli::Key.ENTER
101
  if multi_chat
102
    $vm.focus_window(@chat_room_jid.split("@").first)
103
104
    msg = @friend_name + ": " + msg
  else
105
    $vm.focus_window(@friend_name)
106
107
  end
  @screen.type(msg)
108
109
end

110
111
Then /^I receive a response from my friend( in the multi-user chat)?$/ do |multi_chat|
  if multi_chat
112
    $vm.focus_window(@chat_room_jid.split("@").first)
113
  else
114
    $vm.focus_window(@friend_name)
115
  end
116
117
118
  @screen.wait("PidginFriendExpectedAnswer.png", 20)
end

119
When /^I start an OTR session with my friend$/ do
120
  $vm.focus_window(@friend_name)
121
122
123
124
125
  @screen.click("PidginConversationOTRMenu.png")
  @screen.hide_cursor
  @screen.click("PidginOTRMenuStartSession.png")
end

Tails developers's avatar
Tails developers committed
126
Then /^Pidgin automatically generates an OTR key$/ do
127
128
129
130
131
  @screen.wait("PidginOTRKeyGenPrompt.png", 30)
  @screen.wait_and_click("PidginOTRKeyGenPromptDoneButton.png", 30)
end

Then /^an OTR session was successfully started with my friend$/ do
132
  $vm.focus_window(@friend_name)
133
134
135
  @screen.wait("PidginConversationOTRUnverifiedSessionStarted.png", 10)
end

136
137
138
# The reason the chat must be empty is to guarantee that we don't mix
# up messages/events from other users with the ones we expect from the
# bot.
139
When /^I join some empty multi-user chat$/ do
140
  $vm.focus_window('Buddy List')
141
142
143
144
  @screen.click("PidginBuddiesMenu.png")
  @screen.wait_and_click("PidginBuddiesMenuJoinChat.png", 10)
  @screen.wait_and_click("PidginJoinChatWindow.png", 10)
  @screen.click_mid_right_edge("PidginJoinChatRoomLabel.png")
145
146
147
148
149
150
  account = xmpp_account("Tails_account")
  if account.has_key?("chat_room") && \
     !account["chat_room"].nil? && \
     !account["chat_room"].empty?
    chat_room = account["chat_room"]
  else
Tails developers's avatar
Tails developers committed
151
    chat_room = random_alnum_string(10, 15)
152
  end
153
154
155
156
157
158
159
  @screen.type(chat_room)

  # We will need the conference server later, when starting the bot.
  @screen.click_mid_right_edge("PidginJoinChatServerLabel.png")
  @screen.type("a", Sikuli::KeyModifier.CTRL)
  @screen.type("c", Sikuli::KeyModifier.CTRL)
  conference_server =
160
    $vm.execute_successfully("xclip -o", :user => LIVE_USER).stdout.chomp
Tails developers's avatar
Tails developers committed
161
  @chat_room_jid = chat_room + "@" + conference_server
162
163

  @screen.click("PidginJoinChatButton.png")
164
165
166
167
168
  # The following will both make sure that the we joined the chat, and
  # that it is empty. We'll also deal with the *potential* "Create New
  # Room" prompt that Pidgin shows for some server configurations.
  images = ["PidginCreateNewRoomPrompt.png",
            "PidginChat1UserInRoom.png"]
Tails developers's avatar
Tails developers committed
169
  image_found, _ = @screen.waitAny(images, 30)
170
171
172
  if image_found == "PidginCreateNewRoomPrompt.png"
    @screen.click("PidginCreateNewRoomAcceptDefaultsButton.png")
  end
173
  $vm.focus_window(@chat_room_jid)
174
175
176
  @screen.wait("PidginChat1UserInRoom.png", 10)
end

177
178
179
180
# Since some servers save the scrollback, and sends it when joining,
# it's safer to clear it so we do not get false positives from old
# messages when looking for a particular response, or similar.
When /^I clear the multi-user chat's scrollback$/ do
181
  $vm.focus_window(@chat_room_jid)
182
  @screen.click("PidginConversationMenu.png")
Tails developers's avatar
Tails developers committed
183
  @screen.wait_and_click("PidginConversationMenuClearScrollback.png", 10)
184
185
end

186
Then /^I can see that my friend joined the multi-user chat$/ do
187
  $vm.focus_window(@chat_room_jid)
188
189
190
  @screen.wait("PidginChat2UsersInRoom.png", 60)
end

191
def configured_pidgin_accounts
192
  accounts = Hash.new
193
194
195
  xml = REXML::Document.new(
    $vm.file_content("/home/#{LIVE_USER}/.purple/accounts.xml")
  )
Tails developers's avatar
Tails developers committed
196
  xml.elements.each("account/account") do |e|
197
    account   = e.elements["name"].text
Tails developers's avatar
Tails developers committed
198
199
200
    account_name, network = account.split("@")
    protocol  = e.elements["protocol"].text
    port      = e.elements["settings/setting[@name='port']"].text
201
202
203
204
205
206
207
208
209
210
211
212
    username_element  = e.elements["settings/setting[@name='username']"]
    realname_elemenet = e.elements["settings/setting[@name='realname']"]
    if username_element
      nickname  = username_element.text
    else
      nickname  = nil
    end
    if realname_elemenet
      real_name = realname_elemenet.text
    else
      real_name = nil
    end
213
214
215
216
217
218
219
220
    accounts[network] = {
      'name'      => account_name,
      'network'   => network,
      'protocol'  => protocol,
      'port'      => port,
      'nickname'  => nickname,
      'real_name' => real_name,
    }
221
222
223
224
225
  end

  return accounts
end

226
227
def chan_image (account, channel, image)
  images = {
228
229
    'conference.riseup.net' => {
      'tails' => {
230
231
232
        'conversation_tab' => 'PidginTailsConversationTab',
        'welcome'          => 'PidginTailsChannelWelcome',
      }
233
    },
234
235
236
237
238
239
  }
  return images[account][channel][image] + ".png"
end

def default_chan (account)
  chans = {
240
    'conference.riseup.net' => 'tails',
241
242
243
244
  }
  return chans[account]
end

245
def pidgin_otr_keys
246
  return $vm.file_content("/home/#{LIVE_USER}/.purple/otr.private_key")
247
248
end

249
250
251
252
253
Given /^Pidgin has the expected accounts configured with random nicknames$/ do
  expected = [
            ["irc.oftc.net", "prpl-irc", "6697"],
            ["127.0.0.1",    "prpl-irc", "6668"],
          ]
254
  configured_pidgin_accounts.values.each() do |account|
255
256
257
258
259
260
261
262
    assert(account['nickname'] != "XXX_NICK_XXX", "Nickname was no randomised")
    assert_equal(account['nickname'], account['real_name'],
                 "Nickname and real name are not identical: " +
                 account['nickname'] + " vs. " + account['real_name'])
    assert_equal(account['name'], account['nickname'],
                 "Account name and nickname are not identical: " +
                 account['name'] + " vs. " + account['nickname'])
    candidate = [account['network'], account['protocol'], account['port']]
Tails developers's avatar
Tails developers committed
263
264
265
266
267
268
269
    assert(expected.include?(candidate), "Unexpected account: #{candidate}")
    expected.delete(candidate)
  end
  assert(expected.empty?, "These Pidgin accounts are not configured: " +
         "#{expected}")
end

270
When /^I open Pidgin's account manager window$/ do
271
272
  @screen.wait_and_click('PidginMenuAccounts.png', 20)
  @screen.wait_and_click('PidginMenuManageAccounts.png', 20)
273
274
275
  step "I see Pidgin's account manager window"
end

Tails developers's avatar
Tails developers committed
276
When /^I see Pidgin's account manager window$/ do
Tails developers's avatar
Tails developers committed
277
  @screen.wait("PidginAccountWindow.png", 40)
Tails developers's avatar
Tails developers committed
278
279
end

280
281
282
283
When /^I close Pidgin's account manager window$/ do
  @screen.wait_and_click("PidginAccountManagerCloseButton.png", 10)
end

284
285
286
287
288
289
When /^I close Pidgin$/ do
  $vm.focus_window('Buddy List')
  @screen.type("q", Sikuli::KeyModifier.CTRL)
  @screen.waitVanish('PidginAvailableStatus.png', 10)
end

290
When /^I (de)?activate the "([^"]+)" Pidgin account$/ do |deactivate, account|
Tails developers's avatar
Tails developers committed
291
292
  @screen.click("PidginAccount_#{account}.png")
  @screen.type(Sikuli::Key.LEFT + Sikuli::Key.SPACE)
293
294
295
296
297
298
  if deactivate
    @screen.waitVanish('PidginAccountEnabledCheckbox.png', 5)
  else
    # wait for the Pidgin to be connecting, otherwise sometimes the step
    # that closes the account management dialog happens before the account
    # is actually enabled
299
    @screen.waitAny(['PidginConnecting.png', 'PidginAvailableStatus.png'], 5)
300
  end
Tails developers's avatar
Tails developers committed
301
302
end

303
def deactivate_and_activate_pidgin_account(account)
304
  debug_log("Deactivating and reactivating Pidgin account #{account}")
305
306
307
308
309
310
311
312
313
314
  step "I open Pidgin's account manager window"
  step "I deactivate the \"#{account}\" Pidgin account"
  step "I close Pidgin's account manager window"
  step "I open Pidgin's account manager window"
  step "I activate the \"#{account}\" Pidgin account"
  step "I close Pidgin's account manager window"
end



Tails developers's avatar
Tails developers committed
315
Then /^Pidgin successfully connects to the "([^"]+)" account$/ do |account|
kytv's avatar
kytv committed
316
  expected_channel_entry = chan_image(account, default_chan(account), 'roster')
kytv's avatar
kytv committed
317
  reconnect_button = 'PidginReconnect.png'
anonym's avatar
anonym committed
318
  recovery_on_failure = Proc.new do
319
320
321
322
323
    if @screen.exists('PidginReconnect.png')
      @screen.click('PidginReconnect.png')
    else
      deactivate_and_activate_pidgin_account(account)
    end
anonym's avatar
anonym committed
324
  end
anonym's avatar
anonym committed
325
  retry_tor(recovery_on_failure) do
326
    begin
327
      $vm.focus_window('Buddy List')
kytv's avatar
kytv committed
328
    rescue ExecutionFailedInVM
329
330
331
332
333
      # Sometimes focusing the window with xdotool will fail with the
      # conversation window right on top of it. We'll try to close the
      # conversation window. At worst, the test will still fail...
      close_pidgin_conversation_window(account)
    end
kytv's avatar
kytv committed
334
335
336
    on_screen, _ = @screen.waitAny([expected_channel_entry, reconnect_button], 60)
    unless on_screen == expected_channel_entry
      raise "Connecting to account #{account} failed."
kytv's avatar
kytv committed
337
338
    end
  end
Tails developers's avatar
Tails developers committed
339
340
end

341
Then /^the "([^"]*)" account only responds to PING and VERSION CTCP requests$/ do |irc_server|
342
343
344
345
346
347
348
  ctcp_cmds = [
    "CLIENTINFO", "DATE", "ERRMSG", "FINGER", "PING", "SOURCE", "TIME",
    "USERINFO", "VERSION"
  ]
  expected_ctcp_replies = {
    "PING" => /^\d+$/,
    "VERSION" => /^Purple IRC$/
349
  }
350
351
352
353
  spam_target = configured_pidgin_accounts[irc_server]["nickname"]
  ctcp_check = CtcpChecker.new(irc_server, 6667, spam_target, ctcp_cmds,
                               expected_ctcp_replies)
  ctcp_check.verify_ctcp_responses
354
355
end

356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
Then /^I can join the( pre-configured)? "([^"]+)" channel on "([^"]+)"$/ do |preconfigured, channel, account|
  if preconfigured
    @screen.doubleClick(chan_image(account, channel, 'roster'))
    focus_pidgin_irc_conversation_window(account)
  else
    $vm.focus_window('Buddy List')
    @screen.wait_and_click("PidginBuddiesMenu.png", 20)
    @screen.wait_and_click("PidginBuddiesMenuJoinChat.png", 10)
    @screen.wait_and_click("PidginJoinChatWindow.png", 10)
    @screen.click_mid_right_edge("PidginJoinChatRoomLabel.png")
    @screen.type(channel)
    @screen.click("PidginJoinChatButton.png")
    @chat_room_jid = channel + "@" + account
    $vm.focus_window(@chat_room_jid)
  end
371
  @screen.hide_cursor
372
373
374
375
376
377
378
379
380
381
382
  try_for(60) do
    begin
      @screen.wait_and_click(chan_image(account, channel, 'conversation_tab'), 5)
    rescue FindFailed => e
      # If the channel tab can't be found it could be because there were
      # multiple connection attempts and the channel tab we want is off the
      # screen. We'll try closing tabs until the one we want can be found.
      @screen.type("w", Sikuli::KeyModifier.CTRL)
      raise e
    end
  end
383
  @screen.hide_cursor
Tails developers's avatar
Tails developers committed
384
385
  @screen.wait(          chan_image(account, channel, 'welcome'), 10)
end
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406

Then /^I take note of the configured Pidgin accounts$/ do
  @persistent_pidgin_accounts = configured_pidgin_accounts
end

Then /^I take note of the OTR key for Pidgin's "([^"]+)" account$/ do |account_name|
  @persistent_pidgin_otr_keys = pidgin_otr_keys
end

Then /^Pidgin has the expected persistent accounts configured$/ do
  current_accounts = configured_pidgin_accounts
  assert(current_accounts <=> @persistent_pidgin_accounts,
         "Currently configured Pidgin accounts do not match the persistent ones:\n" +
         "Current:\n#{current_accounts}\n" +
         "Persistent:\n#{@persistent_pidgin_accounts}"
         )
end

Then /^Pidgin has the expected persistent OTR keys$/ do
  assert_equal(pidgin_otr_keys, @persistent_pidgin_otr_keys)
end
407
408
409

def pidgin_add_certificate_from (cert_file)
  # Here, we need a certificate that is not already in the NSS database
410
  step "I copy \"/usr/share/ca-certificates/mozilla/CNNIC_ROOT.crt\" to \"#{cert_file}\" as user \"amnesia\""
411

412
  $vm.focus_window('Buddy List')
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
  @screen.wait_and_click('PidginToolsMenu.png', 10)
  @screen.wait_and_click('PidginCertificatesMenuItem.png', 10)
  @screen.wait('PidginCertificateManagerDialog.png', 10)
  @screen.wait_and_click('PidginCertificateAddButton.png', 10)
  begin
    @screen.wait_and_click('GtkFileChooserDesktopButton.png', 10)
  rescue FindFailed
    # The first time we're run, the file chooser opens in the Recent
    # view, so we have to browse a directory before we can use the
    # "Type file name" button. But on subsequent runs, the file
    # chooser is already in the Desktop directory, so we don't need to
    # do anything. Hence, this noop exception handler.
  end
  @screen.wait_and_click('GtkFileTypeFileNameButton.png', 10)
  @screen.type("l", Sikuli::KeyModifier.ALT) # "Location" field
  @screen.type(cert_file + Sikuli::Key.ENTER)
end

Then /^I can add a certificate from the "([^"]+)" directory to Pidgin$/ do |cert_dir|
  pidgin_add_certificate_from("#{cert_dir}/test.crt")
433
  wait_and_focus('PidginCertificateAddHostnameDialog.png', 10, 'Certificate Import')
434
  @screen.type("XXX test XXX" + Sikuli::Key.ENTER)
435
  wait_and_focus('PidginCertificateTestItem.png', 10, 'Certificate Manager')
436
437
438
439
end

Then /^I cannot add a certificate from the "([^"]+)" directory to Pidgin$/ do |cert_dir|
  pidgin_add_certificate_from("#{cert_dir}/test.crt")
440
  wait_and_focus('PidginCertificateImportFailed.png', 10, 'Import Error')
441
442
443
end

When /^I close Pidgin's certificate manager$/ do
444
  wait_and_focus('PidginCertificateManagerDialog.png', 10, 'Certificate Manager')
445
446
447
448
449
450
451
452
453
454
  @screen.type(Sikuli::Key.ESC)
  # @screen.wait_and_click('PidginCertificateManagerClose.png', 10)
  @screen.waitVanish('PidginCertificateManagerDialog.png', 10)
end

When /^I close Pidgin's certificate import failure dialog$/ do
  @screen.type(Sikuli::Key.ESC)
  # @screen.wait_and_click('PidginCertificateManagerClose.png', 10)
  @screen.waitVanish('PidginCertificateImportFailed.png', 10)
end
455
456

When /^I see the Tails roadmap URL$/ do
457
458
459
460
  try_for(60) do
    begin
      @screen.find('PidginTailsRoadmapUrl.png')
    rescue FindFailed => e
461
      @screen.type(Sikuli::Key.PAGE_UP)
462
463
464
      raise e
    end
  end
465
466
467
468
469
end

When /^I click on the Tails roadmap URL$/ do
  @screen.click('PidginTailsRoadmapUrl.png')
end