/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.esme.api
import scala.xml._
import scala.actors.Actor
import scala.actors.TIMEOUT
import org.specs._
import org.specs.runner.JUnit3
import org.specs.runner.ConsoleRunner
import net.liftweb.actor.LiftActor
import net.liftweb.util._
import net.liftweb.common._
import net.liftweb.mapper.{By}
import org.specs.matcher._
import Helpers._
import org.apache.esme._
import model._
import org.apache.esme.actor.Distributor
import org.apache.esme.actor.UserActor.MessageReceived
import org.apache.esme.actor.Distributor.Listen
import net.liftweb.http._
import testing.{ReportFailure, TestKit, HttpResponse, TestFramework, TestResponse, Response}
import _root_.junit.framework.AssertionFailedError
class TwitterAPISpecsAsTest extends JUnit3(TwitterAPISpecs)
object TwitterAPISpecsRunner extends ConsoleRunner(TwitterAPISpecs)
object TwitterAPISpecs extends Specification with TestKit {
JettyTestServer.start
val baseUrl = JettyTestServer.urlFor(TwitterAPI.ApiPath.mkString("/", "/", ""))
val userName = "twitter_user"
val theUser = find_or_create_user(userName)
val token = find_or_create_token(theUser)
val followerName = "twitter_follower"
val followerUser = find_or_create_user(followerName)
val followerToken = find_or_create_token(followerUser)
implicit val reportError = new ReportFailure {
def fail(msg: String): Nothing = TwitterAPISpecs.this.fail(msg)
}
def find_or_create_user(userName: String): User = {
val users = User.findByNickname(userName)
if(users.length > 0)
users.head
else {
val session = new LiftSession(Helpers.randomString(20), "", Empty)
S.initIfUninitted(session) {User.createAndPopulate.nickname(userName).saveMe}
}
}
def find_or_create_token(tokenUser: User): String = {
val token: Box[AuthToken] = AuthToken.find(By(AuthToken.user,tokenUser))
if(token.isDefined)
token.open_!.uniqueId.is
else {
val token = AuthToken.create.user(tokenUser).saveMe
token.uniqueId.is
}
}
override def theHttpClient = {
val theClient = buildBasicAuthClient(userName, token)
theClient.getParams.setAuthenticationPreemptive(true)
theClient
}
val noAuthClient = buildNoAuthClient
val followerClient = {
val theClient = buildBasicAuthClient(followerName, followerToken)
theClient.getParams.setAuthenticationPreemptive(true)
theClient
}
class BridgeActor(receiver: Actor) extends LiftActor {
protected def messageHandler = {
case nm @ MessageReceived(_, _) => receiver ! nm
}
}
case object Wait
class ConductorActor extends Actor {
def act {
react {
case Wait => reply {
receive {
case MessageReceived(msg, reason) => msg
}
}
}
}
}
// register an actor to be notified when a new message is received
val conductor = new ConductorActor
conductor.start
val liftActor = new BridgeActor(conductor)
Distributor ! Listen(theUser.id.is, liftActor)
Distributor ! Listen(followerUser.id.is, liftActor)
trait XmlResponse {
self: TestResponse =>
def xmlMatch(f: Elem => Unit)(implicit errorFunc: ReportFailure): TestResponse = {
if (this.asInstanceOf[SelfType].code != 200) errorFunc.fail("Response status is not 200!")
xml match {
case Full(xml) => f(xml); this
case _ => errorFunc.fail("Response contains no XML!")
}
}
def \\(node: Node): TestResponse = xmlMatch(_ must XmlBaseMatchers.\\(node))
def !\\(node: Node): TestResponse = xmlMatch(_ must not(XmlBaseMatchers.\\(node)))
def \(node: Node): TestResponse = xmlMatch(_ must XmlBaseMatchers.\(node))
def !\(node: Node): TestResponse = xmlMatch(_ must not(XmlBaseMatchers.\(node)))
def \\(label: String): TestResponse = xmlMatch(_ must XmlBaseMatchers.\\(label))
def !\\(label: String): TestResponse = xmlMatch(_ must not(XmlBaseMatchers.\\(label)))
def \(label: String): TestResponse = xmlMatch(_ must XmlBaseMatchers.\(label))
def !\(label: String): TestResponse = xmlMatch(_ must not(XmlBaseMatchers.\(label)))
}
implicit def testResponse2XmlResponse(response: TestResponse): XmlResponse = {
val r = response.asInstanceOf[response.SelfType]
new response.SelfType(r.baseUrl, r.code, r.msg, r.headers, r.body, r.theHttpClient) with XmlResponse
}
"Twitter API" should {
"post a message" in {
post("/statuses/update.xml", "status" -> "test_msg1") \\(test_msg1)
// wait till the message appears in the timeline
// or fail after 5 seconds
val msgReceived = conductor !? (5000L, Wait)
if (msgReceived.isEmpty) fail("no message received")
}
"fail to post a message with no status parameter" in {
post("/statuses/update.xml") \\("error")
}
"fail to post a message when no user credentials supplied" in {
post("/statuses/update.xml", noAuthClient, Nil, "status" -> "test_msg1") \\("error")
}
"show message in user, home and public timelines" in {
get("/statuses/public_timeline.xml") \\(test_msg1)
get("/statuses/user_timeline.xml") \\(test_msg1)
get("/statuses/home_timeline.xml") \\(test_msg1)
}
"not show message in user and home timelines when not authenticated" in {
get("/statuses/user_timeline.xml", noAuthClient, Nil) \\("error")
get("/statuses/home_timeline.xml", noAuthClient, Nil) \\("error")
}
"create a friendship" in {
post("/friendships/create/" + userName + ".xml", followerClient, Nil) \\(twitter_user)
}
"let user see new follower in followers" in {
get("/statuses/followers.xml") \\(twitter_follower)
}
"not let follower see user in followers" in {
get("/statuses/followers.xml", followerClient, Nil) !\\(twitter_user)
}
"not let user see new follower in friends" in {
get("/statuses/friends.xml") !\\(twitter_follower)
}
"let follower see user in friends" in {
get("/statuses/friends.xml", followerClient, Nil) \\(twitter_user)
}
"let follower see user's message in home timeline" in {
post("/statuses/update.xml", "status" -> "user_msg") \\(user_msg)
// wait till the message appears in the timeline
// or fail after 5 seconds
val msgReceived = conductor !? (5000L, Wait)
if (msgReceived.isEmpty) fail("no message received")
get("/statuses/home_timeline.xml", followerClient, Nil) \\(user_msg)
}
"not let user see follower's message in home timeline" in {
post("/statuses/update.xml", followerClient, Nil, "status" -> "follower_msg") \\(follower_msg)
get("/statuses/home_timeline.xml") !\\(follower_msg)
}
"let user see follower's message in public timeline" in {
get("/statuses/public_timeline.xml") \\(follower_msg)
}
"destroy a friendship" in {
post("/friendships/destroy/" + userName + ".xml", followerClient, Nil) \\(twitter_user)
}
}
}