mp_weixin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|