1require File.expand_path('../test_helper', __FILE__) 2require File.expand_path('../../PassengerPref', __FILE__) 3 4def OSX._ignore_ns_override; true; end 5 6class InstallPassengerWarning < OSX::NSView 7 def initWithTextField 8 if init 9 text_field = OSX::NSTextField.alloc.init 10 text_field.stringValue = "blabla http://www.modrails.com blabla" 11 addSubview text_field 12 self 13 end 14 end 15end 16 17module PrefPanePassengerSpecsHelper 18 def set_apps_controller_content(apps) 19 applicationsController.content = apps 20 applicationsController.selectedObjects = apps 21 end 22 23 def stub_app_controller_with_number_of_apps(number) 24 apps = Array.new(number) do |i| 25 stub("PassengerApplication: #{i}") 26 end 27 set_apps_controller_content(apps) 28 apps 29 end 30 31 def stub_app_controller_with_a_app 32 stub_app_controller_with_number_of_apps(1).first 33 end 34 35 def alert_stub 36 window = stub_everything('Window') 37 alert = stub('Alert') 38 alert.stubs(:window).returns(window) 39 alert 40 end 41end 42 43describe "PrefPanePassenger, while initializing" do 44 tests PrefPanePassenger 45 46 def after_setup 47 ib_outlets :installPassengerWarning => OSX::InstallPassengerWarning.alloc.initWithTextField, 48 :authorizationView => OSX::SFAuthorizationView.alloc.init 49 50 pref_pane.stubs(:paneWillBecomeActive) 51 end 52 53 it "should register itself as the sharedInstance" do 54 pref_pane.mainViewDidLoad 55 PrefPanePassenger.sharedInstance.should.be.instance_of PrefPanePassenger 56 end 57 58 it "should configure the authorization view" do 59 authorizationView.expects(:string=).with(OSX::KAuthorizationRightExecute) 60 pref_pane.mainViewDidLoad 61 authorizationView.delegate.should.be pref_pane 62 assigns(:authorized).should.be false 63 end 64 65 it "should initialize an empty array which will hold the list of applications" do 66 pref_pane.mainViewDidLoad 67 apps = assigns(:applications) 68 apps.should.be.instance_of OSX::NSCFArray 69 apps.should.be.empty 70 end 71 72 it "should register itself for notifications for if the System Preferences.app will be activated" do 73 OSX::NSNotificationCenter.defaultCenter.expects(:objc_send).with( 74 :addObserver, pref_pane, 75 :selector, 'paneWillBecomeActive:', 76 :name, OSX::NSApplicationWillBecomeActiveNotification, 77 :object, nil 78 ) 79 pref_pane.mainViewDidLoad 80 end 81end 82 83describe "PrefPanePassenger, when about to be (re)displayed" do 84 tests PrefPanePassenger 85 86 def after_setup 87 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init, 88 :applicationsTableView => OSX::NSTableView.alloc.init, 89 :installPassengerWarning => OSX::InstallPassengerWarning.alloc.initWithTextField 90 91 pref_pane.stubs(:passenger_installed?).returns(false) 92 end 93 94 it "should enable the 'install passenger' warning in the UI if the Passenger Apache module isn't loaded" do 95 installPassengerWarning.hidden = true 96 pref_pane.paneWillBecomeActive 97 98 installPassengerWarning.hidden?.should.be false 99 end 100 101 it "should disable the 'install passenger' warning in the UI if the Passenger Apache module is loaded" do 102 pref_pane.stubs(:passenger_installed?).returns(true) 103 installPassengerWarning.hidden = false 104 pref_pane.paneWillBecomeActive 105 106 installPassengerWarning.hidden?.should.be true 107 end 108 109 it "should add existing applications found in #{PassengerPaneConfig::PASSENGER_APPS_DIR} to the array controller: applicationsController" do 110 blog_app, paste_app = add_applications! 111 pref_pane.paneWillBecomeActive 112 113 applicationsController.content.should == [blog_app, paste_app] 114 applicationsController.selectedObjects.should == [paste_app] 115 end 116 117 it "should reload loaded applications from disk" do 118 blog_app, paste_app = add_applications! 119 pref_pane.paneWillBecomeActive 120 121 blog_app.expects(:reload!) 122 paste_app.expects(:reload!) 123 pref_pane.willSelect 124 end 125 126 private 127 128 def add_applications! 129 dir = PassengerPaneConfig::PASSENGER_APPS_DIR 130 ext = PassengerPaneConfig::PASSENGER_APPS_EXTENSION 131 blog, paste = ["#{dir}/blog.#{ext}", "#{dir}/paste.#{ext}"] 132 apps = stub("PassengerApplication: blog"), stub("PassengerApplication: paste") 133 PassengerApplication.stubs(:existingApplications).returns(apps) 134 apps 135 end 136end 137 138describe "PrefPanePassenger, while checking for passenger" do 139 tests PrefPanePassenger 140 141 it "should return true if the Passenger Apache modules is loaded" do 142 pref_pane.stubs(:`).with('/usr/sbin/httpd -t -D DUMP_MODULES 2>&1').returns(%{ 143[Fri Jun 20 12:20:03 2008] [warn] _default_ VirtualHost overlap on port 80, the first has precedence 144[Fri Jun 20 12:20:03 2008] [warn] _default_ VirtualHost overlap on port 80, the first has precedence 145Loaded Modules: 146 core_module (static) 147 mpm_prefork_module (static) 148 http_module (static) 149 passenger_module (shared) 150Syntax OK}) 151 152 pref_pane.send(:passenger_installed?).should.be true 153 end 154 155 it "should return false if the Passenger Apache modules is not loaded" do 156 pref_pane.stubs(:`).with('/usr/sbin/httpd -t -D DUMP_MODULES 2>&1').returns(%{ 157[Fri Jun 20 12:20:03 2008] [warn] _default_ VirtualHost overlap on port 80, the first has precedence 158[Fri Jun 20 12:20:03 2008] [warn] _default_ VirtualHost overlap on port 80, the first has precedence 159Loaded Modules: 160 core_module (static) 161 mpm_prefork_module (static) 162 http_module (static) 163Syntax OK}) 164 165 pref_pane.send(:passenger_installed?).should.be false 166 end 167end 168 169describe "PrefPanePassenger, when removing applications" do 170 tests PrefPanePassenger 171 172 def after_setup 173 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init 174 end 175 176 it "should remove the selected applications from the applicationsController" do 177 remove_app, stay_app = stub("PassengerApplication: should be removed"), stub("PassengerApplication: should stay") 178 remove_app.stubs(:new_app?).returns(false) 179 stay_app.stubs(:new_app?).returns(false) 180 PassengerApplication.expects(:removeApplications).with([remove_app]) 181 182 applicationsController.content = [remove_app, stay_app] 183 applicationsController.selectedObjects = [remove_app] 184 185 pref_pane.remove 186 applicationsController.content.should == [stay_app] 187 end 188 189 it "should not try to delete files when removing a new application" do 190 app = PassengerApplication.alloc.init 191 applicationsController.content = [app] 192 applicationsController.selectedObjects = [app] 193 194 PassengerApplication.expects(:removeApplications).times(0) 195 pref_pane.remove 196 applicationsController.content.should.be.empty 197 end 198 199 it "should not open the browse panel after removing applications" do 200 pref_pane.expects(:browse).times(0) 201 pref_pane.setValue_forKey([], 'applications') 202 end 203end 204 205describe "PrefPanePassenger, when adding applications" do 206 tests PrefPanePassenger 207 208 it "should open the browse panel when a new empty application is added to the applications array" do 209 pref_pane.expects(:browse).times(1) 210 pref_pane.setValue_forKey([PassengerApplication.alloc.init], 'applications') 211 212 pref_pane.expects(:browse).times(0) 213 pref_pane.setValue_forKey([PassengerApplication.alloc.init, PassengerApplication.alloc.initWithFile(File.expand_path('../fixtures/blog.vhost.conf', __FILE__))], 'applications') 214 end 215end 216 217describe "PrefPanePassenger, when unselecting the pane" do 218 tests PrefPanePassenger 219 220 include PrefPanePassengerSpecsHelper 221 222 def after_setup 223 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init 224 225 mainView = stub('Main View') 226 pref_pane.stubs(:mainView).returns(mainView) 227 window = stub('Main Window') 228 mainView.stubs(:window).returns(window) 229 230 pref_pane.stubs(:passenger_installed?).returns(true) 231 pref_pane.mainViewDidLoad 232 end 233 234 it "should show a warning if the current selected application is dirty" do 235 set_apps_controller_content [PassengerApplication.alloc.initWithPath('/previous/path/to/Blog')] 236 237 OSX::NSAlert.any_instance.expects(:objc_send).with( 238 :beginSheetModalForWindow, pref_pane.mainView.window, 239 :modalDelegate, pref_pane, 240 :didEndSelector, 'unsavedChangesAlertDidEnd:returnCode:contextInfo:', 241 :contextInfo, nil 242 ).times(1) 243 244 pref_pane.shouldUnselect.should == OSX::NSUnselectLater 245 end 246 247 it "should not show a warning if there are no dirty applications" do 248 assigns(:dirty_apps, false) 249 OSX::NSAlert.any_instance.expects(:objc_send).times(0) 250 pref_pane.shouldUnselect.should == OSX::NSUnselectNow 251 end 252 253 it "should not show a warning if there aren't any applications" do 254 assigns(:dirty_apps, true) 255 applicationsController.content = [] 256 OSX::NSAlert.any_instance.expects(:objc_send).times(0) 257 pref_pane.shouldUnselect.should == OSX::NSUnselectNow 258 end 259 260 it "should save the application and then tell the pane to unselect if the user chooses to apply unsaved changes" do 261 pref_pane.expects(:apply) 262 pref_pane.expects(:replyToShouldUnselect).with(true) 263 pref_pane.unsavedChangesAlertDidEnd_returnCode_contextInfo(alert_stub, PrefPanePassenger::APPLY, nil) 264 end 265 266 it "should tell the pane to not unselect if the user chooses to review unsaved changes" do 267 pref_pane.expects(:apply).times(0) 268 pref_pane.expects(:replyToShouldUnselect).with(false) 269 pref_pane.unsavedChangesAlertDidEnd_returnCode_contextInfo(alert_stub, PrefPanePassenger::CANCEL, nil) 270 end 271 272 it "should remove new and unsaved apps and revert unsaved existing apps and tell the pane to unselect if the user chooses to not apply unsaved changes" do 273 new_app = PassengerApplication.alloc.init 274 existing_app = PassengerApplication.alloc.initWithFile(File.expand_path('../fixtures/blog.vhost.conf', __FILE__)) 275 existing_app.setValue_forKey('foo.local', 'host') 276 277 set_apps_controller_content([new_app, existing_app]) 278 279 PassengerApplication.expects(:removeApplications).times(0) 280 pref_pane.expects(:replyToShouldUnselect).with(true) 281 pref_pane.unsavedChangesAlertDidEnd_returnCode_contextInfo(alert_stub, PrefPanePassenger::DONT_APPLY, nil) 282 283 applicationsController.content.should == [existing_app] 284 applicationsController.content.first.host.should == "het-manfreds-blog.local" 285 end 286end 287 288describe "PrefPanePassenger, when applying changes" do 289 tests PrefPanePassenger 290 291 include PrefPanePassengerSpecsHelper 292 293 def after_setup 294 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init 295 pref_pane.stubs(:authorize!).returns(true) 296 end 297 298 it "should show the authorizationView if necessary" do 299 pref_pane.expects(:authorize!).returns(true) 300 pref_pane.apply 301 end 302 303 it "should send the apply message to all the dirty applications" do 304 apps = stub_app_controller_with_number_of_apps(3) 305 306 apps.first.stubs(:dirty?).returns(false) 307 apps.first.expects(:apply).times(0) 308 309 apps[1..2].each do |app| 310 app.stubs(:dirty?).returns(true) 311 app.expects(:apply).times(1) 312 end 313 314 pref_pane.apply 315 end 316 317 it "should set @dirty_apps and @revertable_apps to false once all unsaved apps received the apply message" do 318 assigns(:dirty_apps, true) 319 assigns(:revertable_apps, true) 320 pref_pane.apply 321 pref_pane.dirty_apps.should.be false 322 pref_pane.revertable_apps.should.be false 323 end 324end 325 326describe "PrefPanePassenger, when reverting changes" do 327 tests PrefPanePassenger 328 329 include PrefPanePassengerSpecsHelper 330 331 def after_setup 332 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init 333 end 334 335 it "should send the revert message to all revertable applications" do 336 apps = stub_app_controller_with_number_of_apps(3) 337 338 apps.first.stubs(:revertable?).returns(false) 339 apps.first.expects(:revert).times(0) 340 341 apps[1..2].each do |app| 342 app.stubs(:revertable?).returns(true) 343 app.expects(:revert).times(1) 344 end 345 346 pref_pane.revert 347 end 348 349 it "should set @dirty_apps and @revertable_apps to false once all unsaved apps received the revert message" do 350 assigns(:dirty_apps, true) 351 assigns(:revertable_apps, true) 352 pref_pane.revert 353 pref_pane.dirty_apps.should.be false 354 pref_pane.revertable_apps.should.be false 355 end 356end 357 358describe "PrefPanePassenger, when restarting applications" do 359 tests PrefPanePassenger 360 361 include PrefPanePassengerSpecsHelper 362 363 def after_setup 364 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init 365 end 366 367 it "should send the restart message to all not new applications" do 368 apps = stub_app_controller_with_number_of_apps(3) 369 370 apps.first.stubs(:new_app?).returns(true) 371 apps.first.expects(:restart).times(0) 372 373 apps[1..2].each do |app| 374 app.stubs(:new_app?).returns(false) 375 app.expects(:restart).times(1) 376 end 377 378 pref_pane.restart 379 end 380end 381 382describe "PrefPanePassenger, when using the directory browse panel" do 383 tests PrefPanePassenger 384 385 def after_setup 386 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init 387 388 mainView = stub('Main View') 389 pref_pane.stubs(:mainView).returns(mainView) 390 window = stub('Main Window') 391 mainView.stubs(:window).returns(window) 392 393 PrefPanePassenger.any_instance.stubs(:applicationMarkedDirty) 394 end 395 396 it "should display the path to the currently selected application" do 397 app = PassengerApplication.alloc.initWithPath('/previous/path/to/Blog') 398 applicationsController.content = [app] 399 applicationsController.selectedObjects = [app] 400 401 pref_pane.send(:path_for_browser).should == '/previous/path/to/Blog' 402 end 403 404 it "should display the home directory if no application is selected" do 405 applicationsController.selectedObjects = [] 406 pref_pane.send(:path_for_browser).to_s.should == File.expand_path('~') 407 end 408 409 it "should set the path to the selected directory as the path for the currently selected application" do 410 app = PassengerApplication.alloc.initWithPath('/previous/path/to/Blog') 411 applicationsController.content = [app] 412 applicationsController.selectedObjects = [app] 413 414 OSX::NSOpenPanel.any_instance.expects(:canChooseDirectories=).with(true) 415 OSX::NSOpenPanel.any_instance.expects(:canChooseFiles=).with(false) 416 OSX::NSOpenPanel.any_instance.expects(:objc_send).with( 417 :beginSheetForDirectory, app.path, 418 :file, nil, 419 :types, nil, 420 :modalForWindow, pref_pane.mainView.window, 421 :modalDelegate, pref_pane, 422 :didEndSelector, 'openPanelDidEnd:returnCode:contextInfo:', 423 :contextInfo, nil 424 ) 425 pref_pane.browse 426 427 panel = stub('NSOpenPanel') 428 panel.stubs(:filename).returns('/some/path/to/Blog') 429 430 app.expects(:setValue_forKey).with('/some/path/to/Blog', 'path') 431 pref_pane.openPanelDidEnd_returnCode_contextInfo(panel, OSX::NSOKButton, nil) 432 end 433 434 it "should remove the new application if the user pressed cancel in the browse panel if it's a new not dirty app" do 435 remove_app = PassengerApplication.alloc.init 436 437 applicationsController.content = [remove_app] 438 applicationsController.selectedObjects = [remove_app] 439 440 pref_pane.openPanelDidEnd_returnCode_contextInfo(nil, OSX::NSCancelButton, nil) 441 applicationsController.content.should.be.empty 442 end 443 444 it "should not remove an application when the user presses cancel in the browse panel if the app is dirty" do 445 stay_app = PassengerApplication.alloc.init 446 stay_app.setValue_forKey('foo.local', 'host') 447 448 applicationsController.content = [stay_app] 449 applicationsController.selectedObjects = [stay_app] 450 451 pref_pane.openPanelDidEnd_returnCode_contextInfo(nil, OSX::NSCancelButton, nil) 452 applicationsController.content.should == [stay_app] 453 end 454end 455 456describe "PrefPanePassenger, with drag and drop support" do 457 tests PrefPanePassenger 458 459 def after_setup 460 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init, 461 :applicationsTableView => OSX::NSTableView.alloc.init, 462 :installPassengerWarning => OSX::InstallPassengerWarning.alloc.initWithTextField 463 464 @tmp = File.expand_path('../tmp') 465 FileUtils.mkdir_p @tmp 466 467 pref_pane.stubs(:passenger_installed?).returns(true) 468 PassengerApplication.stubs(:existingApplications).returns([]) 469 pref_pane.mainViewDidLoad 470 end 471 472 def after_teardown 473 FileUtils.rm_rf @tmp 474 end 475 476 it "should configure the table view to accept drag and drop operations" do 477 applicationsTableView.dataSource.should.be pref_pane 478 applicationsTableView.registeredDraggedTypes.should == [OSX::NSFilenamesPboardType] 479 end 480 481 it "should allow multiple directories to be dropped and always add to the bottom of the list" do 482 assigns(:authorized, true) 483 stub_pb_and_info_with_two_directories 484 485 applicationsTableView.expects(:setDropRow_dropOperation).with(0, OSX::NSTableViewDropAbove) 486 487 pref_pane.tableView_validateDrop_proposedRow_proposedDropOperation(nil, @info, nil, nil).should == OSX::NSDragOperationGeneric 488 end 489 490 it "should not allow files to be dropped" do 491 dir = File.join(@tmp, 'dir') 492 FileUtils.mkdir_p dir 493 file = File.join(@tmp, 'file') 494 `touch #{file}` 495 stub_pb_and_info_with [file, dir] 496 497 pref_pane.tableView_validateDrop_proposedRow_proposedDropOperation(nil, @info, nil, nil).should == OSX::NSDragOperationNone 498 end 499 500 it "should add valid applications to the applicationsController" do 501 stub_pb_and_info_with_two_directories 502 503 PassengerApplication.expects(:startApplications).times(0) 504 pref_pane.tableView_acceptDrop_row_dropOperation(nil, @info, nil, nil) 505 506 apps = applicationsController.content 507 apps.map { |app| app.path }.should == @dirs 508 apps.map { |app| app.host }.should == %w{ app1.local app2.local } 509 apps.all? { |app| app.valid? }.should.be true 510 end 511 512 it "should not allow directories to be dropped if not authorized" do 513 assigns(:authorized, false) 514 pref_pane.tableView_validateDrop_proposedRow_proposedDropOperation(nil, nil, nil, nil).should == OSX::NSDragOperationNone 515 end 516 517 it "should not open the browse panel if directories are dropped" do 518 assigns(:dropping_directories, false) 519 stub_pb_and_info_with_two_directories 520 521 applicationsController.expects(:addObjects).with do |apps| 522 pref_pane.setValue_forKey([PassengerApplication.alloc.init], 'applications') 523 true 524 end 525 526 pref_pane.expects(:browse).times(0) 527 pref_pane.tableView_acceptDrop_row_dropOperation(nil, @info, nil, nil) 528 assigns(:dropping_directories).should.be false 529 end 530 531 it "should allow entries from the table view to be dragged to for instance a text editor" do 532 app1 = PassengerApplication.alloc.init 533 app2 = PassengerApplication.alloc.init 534 app1.host = "app1.local" 535 app2.host = "app2.local" 536 537 applicationsController.content = [app1, app2] 538 applicationsController.selectedObjects = [app1, app2] 539 540 pboard = OSX::NSPasteboard.generalPasteboard 541 allowed = pref_pane.tableView_writeRowsWithIndexes_toPasteboard(nil, OSX::NSIndexSet.indexSetWithIndexesInRange(0..1), pboard) 542 allowed.should.be true 543 pboard.propertyListForType(OSX::NSFilenamesPboardType).should == [app1.config_path, app2.config_path] 544 end 545 546 private 547 548 def stub_pb_and_info_with_two_directories 549 dir1 = File.join(@tmp, 'app1') 550 dir2 = File.join(@tmp, 'app2') 551 @dirs = [dir1, dir2] 552 @dirs.each { |f| FileUtils.mkdir_p f } 553 stub_pb_and_info_with @dirs 554 end 555 556 def stub_pb_and_info_with(files) 557 @pb = stub("NSPasteboard") 558 @info = stub("NSDraggingInfo") 559 @info.stubs(:draggingPasteboard).returns(@pb) 560 @pb.stubs(:propertyListForType).with(OSX::NSFilenamesPboardType).returns(files.to_ns) 561 end 562end 563 564describe "PrefPanePassenger, in general" do 565 tests PrefPanePassenger 566 567 include PrefPanePassengerSpecsHelper 568 569 def after_setup 570 ib_outlets :applicationsController => OSX::NSArrayController.alloc.init, 571 :authorizationView => OSX::SFAuthorizationView.alloc.init 572 573 pref_pane.stubs(:passenger_installed?).returns(true) 574 pref_pane.mainViewDidLoad 575 end 576 577 it "should change the authorized state if a authorization request succeeds" do 578 authorizationView.authorization.expects(:objc_send).with( 579 :permitWithRight, OSX::KAuthorizationRightExecute, 580 :flags, (OSX::KAuthorizationFlagPreAuthorize | OSX::KAuthorizationFlagExtendRights | OSX::KAuthorizationFlagInteractionAllowed) 581 ).returns(0) 582 583 pref_pane.expects(:authorizationViewDidAuthorize).times(1) 584 pref_pane.send(:authorize!).should.be true 585 end 586 587 it "should not change the authorized state if a authorization request fails" do 588 authorizationView.authorization.expects(:objc_send).with( 589 :permitWithRight, OSX::KAuthorizationRightExecute, 590 :flags, (OSX::KAuthorizationFlagPreAuthorize | OSX::KAuthorizationFlagExtendRights | OSX::KAuthorizationFlagInteractionAllowed) 591 ).returns(60007) 592 593 pref_pane.expects(:authorizationViewDidAuthorize).times(0) 594 pref_pane.send(:authorize!).should.be false 595 end 596 597 it "should forward delegate messages from the authorization view to the security helper" do 598 authorization = stub('Authorization Ref') 599 authorizationView.authorization.stubs(:authorizationRef).returns(authorization) 600 pref_pane.authorizationViewDidAuthorize 601 OSX::SecurityHelper.sharedInstance.should.be.authorized 602 assigns(:authorized).should.be true 603 604 pref_pane.authorizationViewDidDeauthorize 605 OSX::SecurityHelper.sharedInstance.should.not.be.authorized 606 assigns(:authorized).should.be false 607 end 608 609 it "should know if there are dirty apps" do 610 app = PassengerApplication.alloc.init 611 set_apps_controller_content([app]) 612 613 app.setValue_forKey('foo.local', 'host') 614 pref_pane.dirty_apps.should.be true 615 end 616end