Home Download Docs Code Community
     1	/*
     2	Copyright 2014 The Perkeep Authors
     3	
     4	Licensed under the Apache License, Version 2.0 (the "License");
     5	you may not use this file except in compliance with the License.
     6	You may obtain a copy of the License at
     7	
     8	     http://www.apache.org/licenses/LICENSE-2.0
     9	
    10	Unless required by applicable law or agreed to in writing, software
    11	distributed under the License is distributed on an "AS IS" BASIS,
    12	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13	See the License for the specific language governing permissions and
    14	limitations under the License.
    15	*/
    16	
    17	package twitter
    18	
    19	import (
    20		"encoding/json"
    21		"fmt"
    22		"log"
    23		"net/http"
    24		"os"
    25		"path/filepath"
    26		"strconv"
    27		"time"
    28	
    29		"perkeep.org/internal/httputil"
    30		"perkeep.org/internal/osutil"
    31		"perkeep.org/pkg/importer"
    32	)
    33	
    34	var _ importer.TestDataMaker = (*imp)(nil)
    35	
    36	func (im *imp) SetTestAccount(acctNode *importer.Object) error {
    37		return acctNode.SetAttrs(
    38			importer.AcctAttrAccessToken, "fakeAccessToken",
    39			importer.AcctAttrAccessTokenSecret, "fakeAccessSecret",
    40			importer.AcctAttrUserID, "fakeUserID",
    41			importer.AcctAttrName, "fakeName",
    42			importer.AcctAttrUserName, "fakeScreenName",
    43		)
    44	}
    45	
    46	func (im *imp) MakeTestData() http.RoundTripper {
    47		const (
    48			fakeMaxId = int64(486450108201201664) // Most recent tweet.
    49			nTweets   = 300                       // Arbitrary number of tweets generated.
    50		)
    51		fakeMinId := fakeMaxId - nTweets // Oldest tweet in our timeline.
    52	
    53		timeLineURL := apiURL + userTimeLineAPIPath
    54		timeLineCached := make(map[int64]string)
    55		okHeader := `HTTP/1.1 200 OK
    56	Content-Type: application/json; charset=UTF-8
    57	
    58	`
    59		timeLineResponse := okHeader + fakeTimeLine(fakeMaxId, fakeMinId, timeLineCached)
    60	
    61		fakePic := fakePicture()
    62		responses := map[string]func() *http.Response{
    63			timeLineURL: httputil.StaticResponder(timeLineResponse),
    64			fmt.Sprintf("%s?count=%d&user_id=fakeUserID", timeLineURL, tweetRequestLimit): httputil.StaticResponder(timeLineResponse),
    65			"https://twitpic.com/show/large/bar":                                          httputil.FileResponder(fakePic),
    66			"https://i.imgur.com/foo.gif":                                                 httputil.FileResponder(fakePic),
    67		}
    68	
    69		// register all the user_timeline calls (max_id varies) that should occur,
    70		responses[fmt.Sprintf("%s?count=%d&max_id=%d&user_id=fakeUserID", timeLineURL, tweetRequestLimit, fakeMaxId-nTweets+1)] = httputil.StaticResponder(okHeader + fakeTimeLine(fakeMaxId-nTweets+1, fakeMinId, timeLineCached))
    71		if nTweets > tweetRequestLimit {
    72			// that is, once every tweetRequestLimit-1, going down from fakeMaxId.
    73			for i := fakeMaxId; i > fakeMinId; i -= tweetRequestLimit - 1 {
    74				responses[fmt.Sprintf("%s?count=%d&max_id=%d&user_id=fakeUserID", timeLineURL, tweetRequestLimit, i)] = httputil.StaticResponder(okHeader + fakeTimeLine(i, fakeMinId, timeLineCached))
    75			}
    76		}
    77	
    78		// register all the possible combinations of media twimg
    79		for _, scheme := range []string{"http://", "https://"} {
    80			for _, picsize := range []string{"thumb", "small", "medium", "large"} {
    81				responses[fmt.Sprintf("%spbs.twimg.com/media/foo.jpg:%s", scheme, picsize)] = httputil.FileResponder(fakePic)
    82				responses[fmt.Sprintf("%spbs.twimg.com/media/bar.png:%s", scheme, picsize)] = httputil.FileResponder(fakePic)
    83			}
    84		}
    85	
    86		return httputil.NewFakeTransport(responses)
    87	}
    88	
    89	// fakeTimeLine returns a JSON user timeline of tweetRequestLimit tweets, starting
    90	// with maxId as the most recent tweet id. It stops before tweetRequestLimit if
    91	// minId is reached. The returned timeline is saved in cached.
    92	func fakeTimeLine(maxId, minId int64, cached map[int64]string) string {
    93		if tl, ok := cached[maxId]; ok {
    94			return tl
    95		}
    96		min := maxId - int64(tweetRequestLimit)
    97		if min <= minId {
    98			min = minId
    99		}
   100		var tweets []*apiTweetItem
   101		entitiesCounter := 0
   102		geoCounter := 0
   103		for i := maxId; i > min; i-- {
   104			tweet := &apiTweetItem{
   105				Id:           strconv.FormatInt(i, 10),
   106				TextStr:      fmt.Sprintf("fakeText %d", i),
   107				CreatedAtStr: time.Now().Format(time.RubyDate),
   108				Entities:     fakeEntities(entitiesCounter),
   109			}
   110			geo, coords := fakeGeo(geoCounter)
   111			tweet.Geo = geo
   112			tweet.Coordinates = coords
   113			tweets = append(tweets, tweet)
   114			entitiesCounter++
   115			geoCounter++
   116			if entitiesCounter == 10 {
   117				entitiesCounter = 0
   118			}
   119			if geoCounter == 5 {
   120				geoCounter = 0
   121			}
   122		}
   123		userTimeLine, err := json.MarshalIndent(tweets, "", "	")
   124		if err != nil {
   125			log.Fatalf("%v", err)
   126		}
   127		cached[maxId] = string(userTimeLine)
   128		return cached[maxId]
   129	}
   130	
   131	func fakeGeo(counter int) (*geo, *coords) {
   132		sf := []float64{37.7447124, -122.4341914}
   133		gre := []float64{45.1822842, 5.7141854}
   134		switch counter {
   135		case 0:
   136			return nil, nil
   137		case 1:
   138			return &geo{sf}, nil
   139		case 2:
   140			return nil, &coords{[]float64{gre[1], gre[0]}}
   141		case 3:
   142			return &geo{gre}, &coords{[]float64{sf[1], sf[0]}}
   143		default:
   144			return nil, nil
   145		}
   146	}
   147	
   148	func fakeEntities(counter int) entities {
   149		sizes := func() map[string]mediaSize {
   150			return map[string]mediaSize{
   151				"medium": {W: 591, H: 332, Resize: "fit"},
   152				"large":  {W: 591, H: 332, Resize: "fit"},
   153				"small":  {W: 338, H: 190, Resize: "fit"},
   154				"thumb":  {W: 150, H: 150, Resize: "crop"},
   155			}
   156		}
   157		mediaTwimg1 := func() *media {
   158			return &media{
   159				Id:            "1",
   160				IdNum:         1,
   161				MediaURL:      `http://pbs.twimg.com/media/foo.jpg`,
   162				MediaURLHTTPS: `https://pbs.twimg.com/media/foo.jpg`,
   163				Sizes:         sizes(),
   164			}
   165		}
   166		mediaTwimg2 := func() *media {
   167			return &media{
   168				Id:            "2",
   169				IdNum:         2,
   170				MediaURL:      `http://pbs.twimg.com/media/bar.png`,
   171				MediaURLHTTPS: `https://pbs.twimg.com/media/bar.png`,
   172				Sizes:         sizes(),
   173			}
   174		}
   175		notPicURL := func() *urlEntity {
   176			return &urlEntity{
   177				URL:         `http://t.co/whatever`,
   178				ExpandedURL: `http://perkeep.org`,
   179				DisplayURL:  `perkeep.org`,
   180			}
   181		}
   182		imgurURL := func() *urlEntity {
   183			return &urlEntity{
   184				URL:         `http://t.co/whatever2`,
   185				ExpandedURL: `http://imgur.com/foo`,
   186				DisplayURL:  `imgur.com/foo`,
   187			}
   188		}
   189		twitpicURL := func() *urlEntity {
   190			return &urlEntity{
   191				URL:         `http://t.co/whatever3`,
   192				ExpandedURL: `http://twitpic.com/bar`,
   193				DisplayURL:  `twitpic.com/bar`,
   194			}
   195		}
   196	
   197		// if you add another case, make sure the entities counter reset
   198		// in fakeTimeLine allows for that case to happen.
   199		// We could use global vars instead, but don't want to pollute the
   200		// twitter pkg namespace.
   201		switch counter {
   202		case 0:
   203			return entities{}
   204		case 1:
   205			return entities{
   206				Media: []*media{
   207					mediaTwimg1(),
   208					mediaTwimg2(),
   209				},
   210			}
   211		case 2:
   212			return entities{
   213				URLs: []*urlEntity{
   214					notPicURL(),
   215				},
   216			}
   217		case 3:
   218			return entities{
   219				URLs: []*urlEntity{
   220					notPicURL(),
   221					imgurURL(),
   222				},
   223			}
   224		case 4:
   225			return entities{
   226				URLs: []*urlEntity{
   227					twitpicURL(),
   228					imgurURL(),
   229				},
   230			}
   231		case 5:
   232			return entities{
   233				Media: []*media{
   234					mediaTwimg2(),
   235					mediaTwimg1(),
   236				},
   237				URLs: []*urlEntity{
   238					notPicURL(),
   239					twitpicURL(),
   240				},
   241			}
   242		case 6:
   243			return entities{
   244				Media: []*media{
   245					mediaTwimg1(),
   246					mediaTwimg2(),
   247				},
   248				URLs: []*urlEntity{
   249					imgurURL(),
   250					twitpicURL(),
   251				},
   252			}
   253		default:
   254			return entities{}
   255		}
   256	}
   257	
   258	func fakePicture() string {
   259		camliDir, err := osutil.GoPackagePath("perkeep.org")
   260		if err == os.ErrNotExist {
   261			log.Fatal("Directory \"perkeep.org\" not found under GOPATH/src; are you not running with devcam?")
   262		}
   263		if err != nil {
   264			log.Fatalf("Error searching for \"perkeep.org\" under GOPATH: %v", err)
   265		}
   266		return filepath.Join(camliDir, filepath.FromSlash("clients/web/embed/glitch/npc_piggy__x1_walk_png_1354829432.png"))
   267	}
Website layout inspired by memcached.
Content by the authors.