mp_weixin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +22 -0
- data/.rspec +7 -0
- data/.travis.yml +20 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +308 -0
- data/Rakefile +31 -0
- data/lib/config/mp_weixin_error.yml +82 -0
- data/lib/mp_weixin.rb +59 -0
- data/lib/mp_weixin/access_token.rb +172 -0
- data/lib/mp_weixin/client.rb +199 -0
- data/lib/mp_weixin/config.rb +36 -0
- data/lib/mp_weixin/error.rb +27 -0
- data/lib/mp_weixin/interface/base.rb +43 -0
- data/lib/mp_weixin/interface/group.rb +92 -0
- data/lib/mp_weixin/interface/menu.rb +73 -0
- data/lib/mp_weixin/interface/message.rb +38 -0
- data/lib/mp_weixin/interface/promotion.rb +48 -0
- data/lib/mp_weixin/interface/user.rb +39 -0
- data/lib/mp_weixin/models/event.rb +123 -0
- data/lib/mp_weixin/models/message.rb +227 -0
- data/lib/mp_weixin/models/reply_message.rb +180 -0
- data/lib/mp_weixin/response.rb +93 -0
- data/lib/mp_weixin/response_rule.rb +46 -0
- data/lib/mp_weixin/server.rb +39 -0
- data/lib/mp_weixin/server_helper.rb +94 -0
- data/lib/mp_weixin/version.rb +3 -0
- data/lib/support/active_model.rb +3 -0
- data/lib/support/active_model/model.rb +99 -0
- data/mp_weixin.gemspec +44 -0
- data/spec/client_spec.rb +87 -0
- data/spec/mp_weixin/access_token_spec.rb +140 -0
- data/spec/mp_weixin/client_spec.rb +111 -0
- data/spec/mp_weixin/config_spec.rb +24 -0
- data/spec/mp_weixin/interface/base_spec.rb +16 -0
- data/spec/mp_weixin/interface/group_spec.rb +133 -0
- data/spec/mp_weixin/interface/menu_spec.rb +72 -0
- data/spec/mp_weixin/interface/message_spec.rb +36 -0
- data/spec/mp_weixin/interface/promotion_spec.rb +48 -0
- data/spec/mp_weixin/interface/user_spec.rb +76 -0
- data/spec/mp_weixin/models/event_spec.rb +94 -0
- data/spec/mp_weixin/models/message_spec.rb +300 -0
- data/spec/mp_weixin/models/reply_message_spec.rb +365 -0
- data/spec/mp_weixin/server_helper_spec.rb +165 -0
- data/spec/mp_weixin/server_spec.rb +56 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/mp_weixin.rb +7 -0
- data/spec/support/rspec_mixin.rb +8 -0
- data/spec/support/weixin.yml +12 -0
- metadata +363 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module MpWeixin
|
3
|
+
module ResponseRule
|
4
|
+
# '接收普通消息', '接收事件推送', '接收语音识别结果'
|
5
|
+
#
|
6
|
+
def handle_request(request, &block)
|
7
|
+
request.body.rewind # in case someone already read it
|
8
|
+
data = request.body.read
|
9
|
+
message = Message.from_xml(data)
|
10
|
+
|
11
|
+
logger.info "Hey, one request from '#{request.url}' been detected, and content is #{message.as_json}"
|
12
|
+
|
13
|
+
if message.present?
|
14
|
+
handle_message(request, message)
|
15
|
+
response_message(request, message, &block)
|
16
|
+
else
|
17
|
+
halt 400, 'unknown message'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# handle corrent data post from weixin
|
22
|
+
#
|
23
|
+
# please @rewrite me
|
24
|
+
def handle_message(request, message)
|
25
|
+
#
|
26
|
+
end
|
27
|
+
|
28
|
+
# 发送被动响应消息'
|
29
|
+
#
|
30
|
+
# please @rewrite me
|
31
|
+
#
|
32
|
+
#
|
33
|
+
# can rely with instance of those class eg, TextReplyMessage, ImageReplyMessage, VoiceReplyMessage
|
34
|
+
# VideoReplyMessage, MusicReplyMessage, NewsReplyMessage
|
35
|
+
# quickly generate reply content through call 'reply_#{msg_type}_message(attributes).to_xml' @see 'spec/mp_weixin/server_helper_spec.rb'
|
36
|
+
#
|
37
|
+
def response_message(request, message, &block)
|
38
|
+
if block_given?
|
39
|
+
block.call(request, message)
|
40
|
+
end
|
41
|
+
|
42
|
+
# reply with
|
43
|
+
# reply_#{msg_type}_message(attributes).to_xml
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module MpWeixin
|
3
|
+
class Server < Sinatra::Base
|
4
|
+
configure :production, :development do
|
5
|
+
enable :logging
|
6
|
+
|
7
|
+
set :haml, { :ugly=>true }
|
8
|
+
set :clean_trace, true
|
9
|
+
Dir.mkdir('log') unless File.exist?('log')
|
10
|
+
|
11
|
+
file = File.new("./log/mp_weixin_#{settings.environment}.log", 'a+')
|
12
|
+
file.sync = true
|
13
|
+
use Rack::CommonLogger, file
|
14
|
+
end
|
15
|
+
|
16
|
+
helpers MpWeixin::ServerHelper, MpWeixin::ResponseRule
|
17
|
+
|
18
|
+
|
19
|
+
before '/' do
|
20
|
+
unless valid_signature?(signature = params[:signature], timestamp = params[:timestamp], nonce= params[:nonce] )
|
21
|
+
halt 401,{'Content-Type' => 'text/plain'}, 'go away!'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# 验证消息真实性
|
26
|
+
#
|
27
|
+
# 通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败
|
28
|
+
# eg curl http://localhost:4567/?nonce=121212121&signature=9dc548e8c7fe32ac53f887e834a8c719a73cafc3×tamp=1388028695&echostr=22222222222222222
|
29
|
+
get '/' do
|
30
|
+
params[:echostr]
|
31
|
+
end
|
32
|
+
|
33
|
+
# '接收普通消息', '发送被动响应消息', '接收事件推送', '接收语音识别结果'
|
34
|
+
post "/" do
|
35
|
+
content_type 'text/xml'
|
36
|
+
handle_request(request)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module MpWeixin
|
3
|
+
module ServerHelper
|
4
|
+
|
5
|
+
# generate a signature string through sha1 encrypt token, timestamp, nonce .
|
6
|
+
#
|
7
|
+
# @param [String] token the token value
|
8
|
+
# @param [String] timestamp the timestamp value from weixin
|
9
|
+
# @param [String] nonce the random num from weixin
|
10
|
+
#
|
11
|
+
# 加密/校验流程如下:
|
12
|
+
# 1. 将token、timestamp、nonce三个参数进行字典序排序
|
13
|
+
# 2. 将三个参数字符串拼接成一个字符串进行sha1加密
|
14
|
+
# 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
def generate_signature(token, timestamp, nonce)
|
18
|
+
signature_content = [token.to_s, timestamp.to_s, nonce.to_s].sort.join("")
|
19
|
+
Digest::SHA1.hexdigest(signature_content)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Whether or not the signature is eql with local_signature
|
23
|
+
#
|
24
|
+
# @param [String] signature the signature value need validate
|
25
|
+
# @param [String] timestamp the timestamp value from weixin
|
26
|
+
# @param [String] nonce the nonce value
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
def valid_signature?(signature, timestamp, nonce)
|
30
|
+
token = Config.token
|
31
|
+
|
32
|
+
local_signature = generate_signature(token,timestamp,nonce)
|
33
|
+
local_signature.eql? signature
|
34
|
+
end
|
35
|
+
|
36
|
+
# initialize an TextReplyMessage
|
37
|
+
# @param [Hash] attributes
|
38
|
+
# @see 'spec/mp_weixin/models/reply_message_spec.rb'
|
39
|
+
def reply_text_message(attributes = {})
|
40
|
+
MpWeixin::TextReplyMessage.new(attributes)
|
41
|
+
end
|
42
|
+
|
43
|
+
# initialize an ImageReplyMessage
|
44
|
+
# @param [Hash] attributes
|
45
|
+
# @see 'spec/mp_weixin/models/reply_message_spec.rb'
|
46
|
+
def reply_image_message(attributes = {}, &block)
|
47
|
+
reply_message = MpWeixin::ImageReplyMessage.new(attributes)
|
48
|
+
block.call(reply_message) if block_given?
|
49
|
+
|
50
|
+
reply_message
|
51
|
+
end
|
52
|
+
|
53
|
+
# initialize an VoiceReplyMessage
|
54
|
+
# @param [Hash] attributes
|
55
|
+
# @see 'spec/mp_weixin/models/reply_message_spec.rb'
|
56
|
+
def reply_voice_message(attributes = {}, &block)
|
57
|
+
reply_message = MpWeixin::VoiceReplyMessage.new(attributes)
|
58
|
+
block.call(reply_message) if block_given?
|
59
|
+
|
60
|
+
reply_message
|
61
|
+
end
|
62
|
+
|
63
|
+
# initialize an VideoReplyMessage
|
64
|
+
# @param [Hash] attributes
|
65
|
+
# @see 'spec/mp_weixin/models/reply_message_spec.rb'
|
66
|
+
def reply_video_message(attributes = {}, &block)
|
67
|
+
reply_message = MpWeixin::VideoReplyMessage.new(attributes)
|
68
|
+
block.call(reply_message) if block_given?
|
69
|
+
|
70
|
+
reply_message
|
71
|
+
end
|
72
|
+
|
73
|
+
# initialize an MusicReplyMessage
|
74
|
+
# @param [Hash] attributes
|
75
|
+
# @see 'spec/mp_weixin/models/reply_message_spec.rb'
|
76
|
+
def reply_music_message(attributes = {}, &block)
|
77
|
+
reply_message = MpWeixin::MusicReplyMessage.new(attributes)
|
78
|
+
block.call(reply_message) if block_given?
|
79
|
+
|
80
|
+
reply_message
|
81
|
+
end
|
82
|
+
|
83
|
+
# initialize an NewsReplyMessage
|
84
|
+
# @param [Hash] attributes
|
85
|
+
# @see 'spec/mp_weixin/models/reply_message_spec.rb'
|
86
|
+
def reply_news_message(attributes = {}, &block)
|
87
|
+
reply_message = MpWeixin::NewsReplyMessage.new(attributes)
|
88
|
+
|
89
|
+
block.call(reply_message) if block_given?
|
90
|
+
|
91
|
+
reply_message
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
|
3
|
+
# == Active \Model Basic \Model
|
4
|
+
#
|
5
|
+
# Includes the required interface for an object to interact with
|
6
|
+
# <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules.
|
7
|
+
# It includes model name introspections, conversions, translations and
|
8
|
+
# validations. Besides that, it allows you to initialize the object with a
|
9
|
+
# hash of attributes, pretty much like <tt>ActiveRecord</tt> does.
|
10
|
+
#
|
11
|
+
# A minimal implementation could be:
|
12
|
+
#
|
13
|
+
# class Person
|
14
|
+
# include ActiveModel::Model
|
15
|
+
# attr_accessor :name, :age
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# person = Person.new(name: 'bob', age: '18')
|
19
|
+
# person.name # => 'bob'
|
20
|
+
# person.age # => 18
|
21
|
+
#
|
22
|
+
# Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
|
23
|
+
# to return +false+, which is the most common case. You may want to override
|
24
|
+
# it in your class to simulate a different scenario:
|
25
|
+
#
|
26
|
+
# class Person
|
27
|
+
# include ActiveModel::Model
|
28
|
+
# attr_accessor :id, :name
|
29
|
+
#
|
30
|
+
# def persisted?
|
31
|
+
# self.id == 1
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# person = Person.new(id: 1, name: 'bob')
|
36
|
+
# person.persisted? # => true
|
37
|
+
#
|
38
|
+
# Also, if for some reason you need to run code on <tt>initialize</tt>, make
|
39
|
+
# sure you call +super+ if you want the attributes hash initialization to
|
40
|
+
# happen.
|
41
|
+
#
|
42
|
+
# class Person
|
43
|
+
# include ActiveModel::Model
|
44
|
+
# attr_accessor :id, :name, :omg
|
45
|
+
#
|
46
|
+
# def initialize(attributes={})
|
47
|
+
# super
|
48
|
+
# @omg ||= true
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# person = Person.new(id: 1, name: 'bob')
|
53
|
+
# person.omg # => true
|
54
|
+
#
|
55
|
+
# For more detailed information on other functionalities available, please
|
56
|
+
# refer to the specific modules included in <tt>ActiveModel::Model</tt>
|
57
|
+
# (see below).
|
58
|
+
module Model
|
59
|
+
def self.included(base) #:nodoc:
|
60
|
+
base.class_eval do
|
61
|
+
extend ActiveModel::Naming
|
62
|
+
extend ActiveModel::Translation
|
63
|
+
include ActiveModel::Validations
|
64
|
+
include ActiveModel::Conversion
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Initializes a new model with the given +params+.
|
69
|
+
#
|
70
|
+
# class Person
|
71
|
+
# include ActiveModel::Model
|
72
|
+
# attr_accessor :name, :age
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# person = Person.new(name: 'bob', age: '18')
|
76
|
+
# person.name # => "bob"
|
77
|
+
# person.age # => 18
|
78
|
+
def initialize(params={})
|
79
|
+
params.each do |attr, value|
|
80
|
+
self.public_send("#{attr}=", value)
|
81
|
+
end if params
|
82
|
+
|
83
|
+
super()
|
84
|
+
end
|
85
|
+
|
86
|
+
# Indicates if the model is persisted. Default is +false+.
|
87
|
+
#
|
88
|
+
# class Person
|
89
|
+
# include ActiveModel::Model
|
90
|
+
# attr_accessor :id, :name
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# person = Person.new(id: 1, name: 'bob')
|
94
|
+
# person.persisted? # => false
|
95
|
+
def persisted?
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/mp_weixin.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mp_weixin/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mp_weixin"
|
8
|
+
spec.version = MpWeixin::VERSION
|
9
|
+
spec.authors = ["jhjguxin"]
|
10
|
+
spec.email = ["864248765@qq.com"]
|
11
|
+
spec.description = %q{A wrapper for weiXin MP platform}
|
12
|
+
spec.summary = %q{A Ruby wrapper for weixin MP platform}
|
13
|
+
spec.homepage = "https://github.com/jhjguxin/mp_weixin"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "oauth2", [">= 0.5","<= 0.9"]
|
22
|
+
spec.add_dependency "sinatra", "~> 1.4.4"
|
23
|
+
spec.add_dependency "activemodel", [">= 3.0","<= 4"]
|
24
|
+
spec.add_dependency 'multi_json', '>= 1.7.9'
|
25
|
+
spec.add_dependency 'multi_xml', '>= 0.5.2'
|
26
|
+
spec.add_dependency 'roxml'
|
27
|
+
spec.add_dependency 'nestful'
|
28
|
+
# spec.add_dependency "data_mapper", "~> 1.2.0"
|
29
|
+
# spec.add_dependency "dm-sqlite-adapter", "~> 1.2.0"
|
30
|
+
|
31
|
+
spec.add_development_dependency "thin"
|
32
|
+
spec.add_development_dependency "debugger"
|
33
|
+
spec.add_development_dependency "bundler"
|
34
|
+
spec.add_development_dependency "rake"
|
35
|
+
spec.add_development_dependency 'rspec'
|
36
|
+
spec.add_development_dependency 'rack-test'
|
37
|
+
# Code coverage for Ruby 1.9+ with a powerful configuration library and automatic merging of coverage across test suites
|
38
|
+
spec.add_development_dependency 'simplecov'
|
39
|
+
# WebMock allows stubbing HTTP requests and setting expectations on HTTP requests.
|
40
|
+
spec.add_development_dependency 'webmock'
|
41
|
+
# A Ruby implementation of the Coveralls API.
|
42
|
+
spec.add_development_dependency "coveralls"
|
43
|
+
spec.add_development_dependency "travis-lint"
|
44
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe MpWeixin::Client do
|
5
|
+
let(:client) { MpWeixin::Client.new }
|
6
|
+
let(:access_token) { 'ACCESS_TOKEN' }
|
7
|
+
let(:token_hash) { {"expires_in" => "7200", "access_token" => access_token} }
|
8
|
+
|
9
|
+
|
10
|
+
context "#initialize config" do
|
11
|
+
it "should have correct site" do
|
12
|
+
client.site.should eq("https://api.weixin.qq.com/")
|
13
|
+
end
|
14
|
+
|
15
|
+
# it "should have correct authorize url" do
|
16
|
+
# client.options[:authorize_url].should eq('/oauth/authorize')
|
17
|
+
# end
|
18
|
+
|
19
|
+
it "should have correct token url" do
|
20
|
+
client.options[:token_url].should eq('/cgi-bin/token')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'is_authorized? should been false' do
|
24
|
+
expect(subject.is_authorized?).to eq(false)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "#taken_code" do
|
29
|
+
let(:json_token) {MultiJson.encode(:expires_in => 7200, :access_token => 'salmon')}
|
30
|
+
|
31
|
+
let(:client) do
|
32
|
+
MpWeixin::Client.new do |builder|
|
33
|
+
builder.request :url_encoded
|
34
|
+
builder.adapter :test do |stub|
|
35
|
+
stub.get("/cgi-bin/token") do |env|
|
36
|
+
[200, {'Content-Type' => 'application/json'}, json_token]
|
37
|
+
end
|
38
|
+
stub.post('/cgi-bin/token') do |env|
|
39
|
+
[200, {'Content-Type' => 'application/json'}, json_token]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
subject do
|
46
|
+
client.get_token
|
47
|
+
client
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
it "returns AccessToken with #token" do
|
52
|
+
expect(subject.token.token).to eq('salmon')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'is_authorized? should been true' do
|
56
|
+
expect(subject.is_authorized?).to eq(true)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "get_token_from_hash" do
|
61
|
+
subject { client.get_token_from_hash(token_hash) }
|
62
|
+
|
63
|
+
it "return token the initalized AccessToken" do
|
64
|
+
expect(subject).to be_a(MpWeixin::AccessToken)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "return token with provide access_token" do
|
68
|
+
expect(subject.token).to eq(access_token)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "#from_hash" do
|
73
|
+
subject { MpWeixin::Client.from_hash(token_hash) }
|
74
|
+
|
75
|
+
it "return client the initalized Client" do
|
76
|
+
expect(subject).to be_a(MpWeixin::Client)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "return token with provide access_token" do
|
80
|
+
expect(subject.token.token).to eq(access_token)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'is_authorized? should been true' do
|
84
|
+
expect(subject.is_authorized?).to eq(true)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe MpWeixin::AccessToken do
|
5
|
+
let(:token) {'ACCESS_TOKEN'}
|
6
|
+
let(:refresh_token) {'REFRESH_TOKEN'}
|
7
|
+
let(:token_body) {MultiJson.encode(:access_token => 'ACCESS_TOKEN', :expires_in => 7200)}
|
8
|
+
let(:client) do
|
9
|
+
MpWeixin::Client.new do |builder|
|
10
|
+
builder.request :url_encoded
|
11
|
+
builder.adapter :test do |stub|
|
12
|
+
stub.send(:get, "/cgi-bin/token") {|env| [200, {'Content-Type' => 'application/json'}, token_body]}
|
13
|
+
stub.send(:post, "/cgi-bin/token") {|env| [200, {'Content-Type' => 'application/json'}, token_body]}
|
14
|
+
|
15
|
+
# stub.post('/sns/oauth2/access_token') {|env| [200, {'Content-Type' => 'application/json'}, refresh_body]}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
subject {MpWeixin::AccessToken.new(client, token)}
|
21
|
+
|
22
|
+
describe "#initialize" do
|
23
|
+
it "assigns client and token" do
|
24
|
+
expect(subject.client).to eq(client)
|
25
|
+
expect(subject.token).to eq(token)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "assigns extra params" do
|
29
|
+
target = MpWeixin::AccessToken.new(client, token, 'foo' => 'bar')
|
30
|
+
expect(target.params).to include('foo')
|
31
|
+
expect(target.params['foo']).to eq('bar')
|
32
|
+
end
|
33
|
+
|
34
|
+
def assert_initialized_token(target)
|
35
|
+
expect(target.token).to eq(token)
|
36
|
+
expect(target).to be_expires
|
37
|
+
expect(target.params.keys).to include('foo')
|
38
|
+
expect(target.params['foo']).to eq('bar')
|
39
|
+
end
|
40
|
+
|
41
|
+
it "initializes with a Hash" do
|
42
|
+
hash = {:access_token => token, :expires_in => Time.now.to_i + 200, 'foo' => 'bar'}
|
43
|
+
target = MpWeixin::AccessToken.from_hash(client, hash)
|
44
|
+
assert_initialized_token(target)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "initializes with a string expires_in" do
|
48
|
+
hash = {:access_token => token, :expires_in => '1361396829', 'foo' => 'bar'}
|
49
|
+
target = MpWeixin::AccessToken.from_hash(client, hash)
|
50
|
+
assert_initialized_token(target)
|
51
|
+
expect(target.expires_in).to be_a(Integer)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#request" do
|
56
|
+
|
57
|
+
context ":mode => :body" do
|
58
|
+
before do
|
59
|
+
subject.options[:mode] = :body
|
60
|
+
end
|
61
|
+
|
62
|
+
it "sends the token in the Authorization header for a GET request" do
|
63
|
+
expect(subject.post("/cgi-bin/token").body).to include(token)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "sends the token in the Authorization header for a POST request" do
|
67
|
+
expect(subject.post("/cgi-bin/token").body).to include(token)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#expires?" do
|
73
|
+
it "is false if there is no expires_at" do
|
74
|
+
expect(MpWeixin::AccessToken.new(client, token)).not_to be_expires
|
75
|
+
end
|
76
|
+
|
77
|
+
it "is true if there is an expires_in" do
|
78
|
+
expect(MpWeixin::AccessToken.new(client, token, :refresh_token => 'REFRESH_TOKEN', :expires_in => 600)).to be_expires
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#expired?" do
|
84
|
+
it "is false if there is no expires_in or expires_at" do
|
85
|
+
expect(MpWeixin::AccessToken.new(client, token)).not_to be_expired
|
86
|
+
end
|
87
|
+
|
88
|
+
it "is false if expires_in is in the future" do
|
89
|
+
expect(MpWeixin::AccessToken.new(client, token, :refresh_token => 'REFRESH_TOKEN', :expires_in => 10800)).not_to be_expired
|
90
|
+
end
|
91
|
+
|
92
|
+
it "is true if expires_at is in the past" do
|
93
|
+
access = MpWeixin::AccessToken.new(client, token, :refresh_token => 'REFRESH_TOKEN', :expires_in => 7200)
|
94
|
+
@now = Time.now + 10800
|
95
|
+
|
96
|
+
# You can't double a constance by either allow or double. Instead you need to use stub_const
|
97
|
+
# https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/mutating-constants
|
98
|
+
|
99
|
+
# Make a mock of Notifier at first
|
100
|
+
stub_const "Time", Time
|
101
|
+
# Then stub the methods of Notifier
|
102
|
+
Time.stub(:now) { @now }
|
103
|
+
|
104
|
+
expect(access).to be_expired
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#refresh!" do
|
110
|
+
let(:access) {
|
111
|
+
MpWeixin::AccessToken.new(client, token, :refresh_token => 'REFRESH_TOKEN',
|
112
|
+
:expires_in => 7200,
|
113
|
+
:param_name => 'o_param')
|
114
|
+
}
|
115
|
+
|
116
|
+
it "returns a refresh token with appropriate values carried over" do
|
117
|
+
refreshed = access.refresh!
|
118
|
+
expect(access.client).to eq(refreshed.client)
|
119
|
+
expect(access.options[:param_name]).to eq(refreshed.options[:param_name])
|
120
|
+
end
|
121
|
+
|
122
|
+
context "with a nil refresh_token in the response" do
|
123
|
+
let(:refresh_body) { MultiJson.encode(:access_token => 'refreshed_foo', :expires_in => 600, :refresh_token => nil) }
|
124
|
+
|
125
|
+
it "copies the refresh_token from the original token" do
|
126
|
+
refreshed = access.refresh!
|
127
|
+
|
128
|
+
expect(refreshed.refresh_token).to eq(access.refresh_token)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#to_hash' do
|
134
|
+
it 'return a hash equals to the hash used to initialize access token' do
|
135
|
+
hash = {:access_token => token, :refresh_token => 'foobar', :expires_at => Time.now.to_i + 200, 'foo' => 'bar'}
|
136
|
+
access_token = MpWeixin::AccessToken.from_hash(client, hash.dup)
|
137
|
+
expect(access_token.to_hash).to eq(hash)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|