motion-wechat 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a409062ddf8847ec76ba44d7ae20f22a348dd64
4
- data.tar.gz: 05889ac31ada26aeff34b322394079af15225937
3
+ metadata.gz: a19b83c93b6167b56d74f08b976f73c5156af0f7
4
+ data.tar.gz: 8cbb139358b5a526d01faa0f29bedb8556a4268b
5
5
  SHA512:
6
- metadata.gz: 0dc82eb924adcabbb06435befe515d61abcc3d5f8d83f90ee66e4e7a626ee90329f4385f88fed4bb9e30a2b3b6e40d7daa78a36131a0b418643a2e4085b04322
7
- data.tar.gz: af65871c625af76f838d17f8dbcc745ffa1cc544364fefa59773365d955ad2673a69abd2f85d7976870f26765f1b3c19388ee8ec9939ff7ef7b5aa273be75d08
6
+ metadata.gz: a9eed5cd35a4eedb2e812f4f99b0eb033ffe5e93a70105b5081e7de0d90f9cbe90959417351c303c4d1bd88e8a471d7748b1c6bf9364273de299c7fd4c2f1149
7
+ data.tar.gz: 4f99a7c8c256c55415292459e0b40253fa7c44ba9fa76dd17e40d1bd7105033c7fa829c887359d1e7b40f7fa413120247f6573a7f50814d6475e6abc1fd43eba
data/README.md CHANGED
@@ -24,6 +24,12 @@ Then setup configuration in your Rakefile:
24
24
  MotionWechat::Config.setup(app, 'app_key', 'app_secret')
25
25
  ```
26
26
 
27
+ Then register app in your `app_delegate.rb`
28
+ ```ruby
29
+ MotionWechat::API.instance.registerApp @key
30
+ ```
31
+
32
+
27
33
  Usage
28
34
  ==========
29
35
 
@@ -34,8 +40,41 @@ MotionWechat::API.instance.send_webpage "http://www.rubymotion.com", \
34
40
  title: "Ruby Motion", description: "Awesome way to write ios/osx app"
35
41
  ```
36
42
 
43
+ Wechat Authorization:
44
+
45
+ #### 1. authorize, this will direct user to wechat app
46
+ ```ruby
47
+ MotionWechat::API.instance.authorize
48
+ ```
49
+
50
+ #### 2. then make sure you have weixin delegate set up in `app_delegate.rb`
51
+ ```ruby
52
+ def application(application, handleOpenURL:url)
53
+ WXApi.handleOpenURL(url, delegate:self)
54
+ end
55
+
56
+ def application(application, openURL:url, sourceApplication:sourceApplication, annotation:annotation)
57
+ WXApi.handleOpenURL(url, delegate:self)
58
+ end
59
+ ```
60
+
61
+ #### 3. set up `onResp` in `app_delegate.rb`. this is when it comes back from wexin app
62
+ ```ruby
63
+ def onResp(resp)
64
+ if resp.is_a? SendAuthResp
65
+ # token lang country code
66
+ MotionWechat::API.instance.registerClient resp.code
67
+ MotionWechat::API.instance.get_user_info do |info|
68
+ p info
69
+ end
70
+ end
71
+ end
72
+ ```
73
+
74
+
37
75
  ## TODO
38
76
  - delegate helpers, i.e. `WXApi.handleOpenURL(url, delegate:self)`
77
+ - state & key validating in `application(application, handleOpenURL:url)`
39
78
 
40
79
  ## Contributions
41
80
 
data/lib/motion-wechat.rb CHANGED
@@ -2,6 +2,8 @@ unless defined?(Motion::Project::Config)
2
2
  raise "This file must be required within a RubyMotion project Rakefile."
3
3
  end
4
4
 
5
+ require 'afmotion'
6
+ require 'bubble-wrap/core'
5
7
  require_relative 'motion-wechat/config'
6
8
 
7
9
  Motion::Project::App.setup do |app|
@@ -11,4 +13,9 @@ Motion::Project::App.setup do |app|
11
13
 
12
14
  app.vendor_project 'vendor/SDKExport', :static
13
15
 
16
+ app.pods ||= Motion::Project::CocoaPods.new(app)
17
+ app.pods do
18
+ pod 'AFNetworking'
19
+ end
20
+
14
21
  end
@@ -0,0 +1,86 @@
1
+ module MotionWechat
2
+ class AccessToken
3
+ attr_reader :client, :token, :expires_in, :openid, :params
4
+ attr_accessor :options, :refresh_token
5
+
6
+ class << self
7
+
8
+ # Initializes an AccessToken from a Hash
9
+ #
10
+ # Example:
11
+ # MotionWechat::AccessToken.from_hash @client, {}
12
+ #
13
+ # Arguments:
14
+ # client: (MotionWechat::Client)
15
+ # hash: (Hash)
16
+ #
17
+ def from_hash(client, hash)
18
+ new(client, hash.delete('access_token') || hash.delete(:access_token), hash)
19
+ end
20
+
21
+ end
22
+
23
+ # Initalize an AccessToken
24
+ #
25
+ # Example:
26
+ # MotionWechat::AccessToken.new @client, 'token', {}
27
+ #
28
+ # Arguments:
29
+ # client: (MotionWechat::Client)
30
+ # token: (String)
31
+ # hash: (Hash)
32
+ #
33
+ def initialize(client, token, opts = {})
34
+ @client = client
35
+ @token = token.to_s
36
+ [:refresh_token, :expires_in, :openid].each do |arg|
37
+ instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
38
+ end
39
+ @expires_in ||= opts.delete('expires')
40
+ @expires_at ||= Time.now.to_i + @expires_in.to_i
41
+ @params = opts
42
+ end
43
+
44
+ # Get information using access token
45
+ #
46
+ # Example:
47
+ # @token.get "/sns/userinfo", { |res| ... }
48
+ #
49
+ # Arguments:
50
+ # path: (String)
51
+ # opts: (hash)
52
+ #
53
+ def get(path, opts = {}, &block)
54
+ @client.get path, opts.merge({access_token: @token}), &block
55
+ end
56
+
57
+ # Get user information
58
+ #
59
+ # Example:
60
+ # @token.get_user_info { |info| ... }
61
+ #
62
+ def get_user_info(&block)
63
+ get "/sns/userinfo", openid: @openid do |info|
64
+ block.call oauth2_wrapp(info)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ # Wrap with OAuth2 structure
71
+ def oauth2_wrapp(info)
72
+ {
73
+ uid: @openid,
74
+ provider: "weixin",
75
+ info: {
76
+ image: info['headimgurl'],
77
+ nickname: info['nickname'],
78
+ name: info['nickname']
79
+ },
80
+ extra: info
81
+ }
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -2,13 +2,80 @@ module MotionWechat
2
2
  module API
3
3
 
4
4
  InvalidMediaObject = Class.new StandardError
5
+ InvalidClientError = Class.new ArgumentError
6
+
5
7
  def wx; WXApi; end
6
8
 
7
- # initialize weixin API using key and secret
9
+ # Initialize weixin API using key and secret
10
+ #
11
+ # Example:
12
+ # MtionWechat::API.new 'key', 'secret'
13
+ #
14
+ # Arguments:
15
+ # key: (String)
16
+ # secret: (String)
17
+ # options: (Hash)
18
+ #
8
19
  def initialize(key, secret, options={})
9
20
  @key = key
10
21
  @secret = secret
11
- WXApi.registerApp(@key)
22
+ end
23
+
24
+ # Register weixin app, usually put in `app_delegate.rb`
25
+ #
26
+ # Example:
27
+ # MotionWechat::API.instance.registerApp
28
+ #
29
+ def registerApp
30
+ WXApi.registerApp @key
31
+ end
32
+
33
+ # Register client
34
+ #
35
+ # Example:
36
+ # MotionWechat::API.instance.registerClient "code"
37
+ #
38
+ # Arguments:
39
+ # code: (String)
40
+ #
41
+ def registerClient(code)
42
+ @client ||= MotionWechat::Client.new @key, @secret, code
43
+ end
44
+
45
+ # Get user information
46
+ #
47
+ # Example:
48
+ # MotionWechat::API.instance.get_user_info { |info| ... }
49
+ #
50
+ def get_user_info(&block)
51
+ raise InvalidClientError if @client.nil?
52
+ if @token.nil?
53
+ @client.get_token do |token|
54
+ @token = token
55
+ @token.get_user_info { |info| block.call info }
56
+ end
57
+ else
58
+ @token.get_user_info { |info| block.call info }
59
+ end
60
+ end
61
+
62
+ # Sends authorization request
63
+ #
64
+ # Example:
65
+ # MotionWechat::API.instance.authorize
66
+ #
67
+ # Arguments:
68
+ # opts: (Hash), state: "myapp"
69
+ #
70
+ def authorize(opts={})
71
+ options = {
72
+ scope: "snsapi_userinfo",
73
+ state: "motion-wechat-app"
74
+ }.merge(opts)
75
+ req = SendAuthReq.alloc.init
76
+ req.scope = options[:scope]
77
+ req.state = options[:state]
78
+ WXApi.sendReq(req)
12
79
  end
13
80
 
14
81
  # Returns singleton instance of MotionWechat
@@ -20,6 +87,11 @@ module MotionWechat
20
87
  @instance ||= new config["key"], config["secret"]
21
88
  end
22
89
 
90
+ # Returns info_plist_key of MotionWechat
91
+ #
92
+ # Example:
93
+ # MotionWechat::API.config
94
+ #
23
95
  def self.config
24
96
  NSBundle.mainBundle.objectForInfoDictionaryKey MotionWechat::Config.info_plist_key
25
97
  end
@@ -0,0 +1,75 @@
1
+ module MotionWechat
2
+ class Client
3
+ attr_reader :id, :secret, :site
4
+ attr_accessor :options
5
+
6
+ # Instantiate a new OAuth 2.0 client using the Client ID
7
+ # and Client Secret registered to your application.
8
+ #
9
+ # Example:
10
+ # MtionWechat::Client.new 'key', 'secret', 'code', {}
11
+ #
12
+ # Arguments:
13
+ # key: (String)
14
+ # secret: (String)
15
+ # code: (String)
16
+ # options: (Hash)
17
+ #
18
+ def initialize(client_id, client_secret, code, options = {})
19
+ opts = options.dup
20
+ @id = client_id
21
+ @secret = client_secret
22
+ @code = code
23
+ @site = opts[:site] || "https://api.weixin.qq.com"
24
+ @options = {
25
+ token_url: '/sns/oauth2/access_token'
26
+ }.merge(opts)
27
+ end
28
+
29
+ # Returns token url to get access token
30
+ #
31
+ # Example:
32
+ # @client.token_url
33
+ #
34
+ def token_url
35
+ @site + @options[:token_url]
36
+ end
37
+
38
+ # Returns token url to get access token
39
+ #
40
+ # Example:
41
+ # @client.get_token do |token|
42
+ # p token
43
+ # end
44
+ #
45
+ def get_token(&block)
46
+ params = "appid=#{@id}&secret=#{@secret}&code=#{@code}&grant_type=authorization_code"
47
+ AFMotion::HTTP.get(token_url + "?" + params) do |res|
48
+ hash = to_hash res.body.to_s
49
+ token = AccessToken.from_hash self, hash
50
+ block.call token
51
+ end
52
+ end
53
+
54
+ # Request information
55
+ #
56
+ # Example:
57
+ # @client.get '/sns/userinfo', openid: "openid" do |info|
58
+ # p info
59
+ # end
60
+ #
61
+ def get(path, opts = {}, &block)
62
+ AFMotion::HTTP.get(@site + path, opts) do |res|
63
+ hash = to_hash res.body.to_s
64
+ block.call hash
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def to_hash(string)
71
+ BW::JSON.parse string.dataUsingEncoding(NSString.defaultCStringEncoding)
72
+ end
73
+
74
+ end
75
+ end
@@ -2,9 +2,8 @@ module MotionWechat
2
2
  module Config
3
3
  extend self
4
4
 
5
- def info_plist_key
6
- "WechatConfig"
7
- end
5
+ def info_plist_key; "WechatConfig"; end
6
+ def key; @key; end
8
7
 
9
8
  # Setup wechat information to rubymotion app, i.e. key, secret...
10
9
  #
@@ -20,13 +19,11 @@ module MotionWechat
20
19
  # Wechat key: (String)
21
20
  # Wechat secret: (String)
22
21
  # other options: (Hash)
23
- def setup(app, key, secret, opts={})
24
- options = {
25
- key: key,
26
- secret: secret
27
- }.merge(opts)
22
+ def setup(app, key, secret)
23
+ @key = key
24
+ @secret = secret
28
25
 
29
- app.info_plist[info_plist_key] = options.select { |k| k == :key || k == :secret }
26
+ app.info_plist[info_plist_key] = { key: @key, secret: @secret }
30
27
 
31
28
  bundle_url_types = [
32
29
  { 'CFBundleURLName' => "weixin",
@@ -34,8 +31,8 @@ module MotionWechat
34
31
  ]
35
32
 
36
33
  app.info_plist['CFBundleURLTypes'] ||= []
37
- app.info_plist['CFBundleURLTypes'] << bundle_url_types
38
-
34
+ app.info_plist['CFBundleURLTypes'] += bundle_url_types
35
+
39
36
  end
40
37
 
41
38
  end
@@ -1,3 +1,3 @@
1
1
  module MotionWechat
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
@@ -0,0 +1,69 @@
1
+ describe MotionWechat::AccessToken do
2
+ extend WebStub::SpecHelpers
3
+
4
+ before do
5
+ disable_network_access!
6
+ hash = {
7
+ access_token: "access_token",
8
+ expires_in:7200,
9
+ refresh_token:"refresh_token",
10
+ openid:"openid",
11
+ scope:"snsapi_userinfo"
12
+ }
13
+ @client = MotionWechat::Client.new "appid", "appsecret", "code"
14
+ @token = MotionWechat::AccessToken.from_hash @client, hash
15
+ end
16
+
17
+ it "should get client, token and expires" do
18
+ @token.client.should.be.kind_of MotionWechat::Client
19
+ @token.token.should == "access_token"
20
+ @token.expires_in.should == 7200
21
+ @token.openid.should == "openid"
22
+ end
23
+
24
+ context "get info" do
25
+
26
+ before do
27
+ @res = File.read NSBundle.mainBundle.resourcePath.stringByAppendingPathComponent("user.json")
28
+ @url = "https://api.weixin.qq.com/sns/userinfo?access_token=access_token&openid=openid"
29
+ end
30
+
31
+ it "gets user info using get" do
32
+ stub_request(:get, @url).
33
+ to_return(json: @res)
34
+ @res = nil
35
+
36
+ @token.get("/sns/userinfo", openid: "openid") do |info|
37
+ @info = info
38
+ resume
39
+ end
40
+
41
+ wait_max 1.0 do
42
+ @info.should.be.kind_of Hash
43
+ @info["nickname"].should == "monkey"
44
+ end
45
+ end
46
+
47
+ it "gets user info using get" do
48
+ stub_request(:get, @url).
49
+ to_return(json: @res)
50
+ @res = nil
51
+
52
+ @token.get_user_info do |info|
53
+ @info = info
54
+ resume
55
+ end
56
+
57
+ wait_max 1.0 do
58
+ @info.should.be.kind_of Hash
59
+ @info[:uid].should == "openid"
60
+ @info[:provider].should == "weixin"
61
+ @info[:info][:image].should == "http://wx.qlogo.cn/mmopen/0"
62
+ @info[:info][:nickname].should == "monkey"
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+
69
+ end
@@ -4,7 +4,7 @@ describe MotionWechat::API do
4
4
  WXApi.stub!(:registerApp) do |key|
5
5
  key.should == "app_key"
6
6
  end
7
- MotionWechat::API.instance
7
+ MotionWechat::API.instance.registerApp
8
8
  end
9
9
 
10
10
  it "should return WXApi as wx" do
@@ -15,6 +15,14 @@ describe MotionWechat::API do
15
15
 
16
16
  before { @mv = MotionWechat::API.instance }
17
17
 
18
+ it "sends auth message" do
19
+ WXApi.stub!(:sendReq) do |req|
20
+ req.should.be.kind_of(SendAuthReq)
21
+ end
22
+
23
+ @mv.authorize
24
+ end
25
+
18
26
  it 'sends web page url' do
19
27
  WXApi.stub!(:sendReq) do |req|
20
28
  req.should.be.kind_of(SendMessageToWXReq)
@@ -23,7 +31,7 @@ describe MotionWechat::API do
23
31
  req.message.mediaObject.webpageUrl.should == "http://www.motion-wechat.com"
24
32
  end
25
33
 
26
- MotionWechat::API.instance.send_webpage "http://www.motion-wechat.com", \
34
+ @mv.send_webpage "http://www.motion-wechat.com", \
27
35
  title: "title", description: "description"
28
36
  end
29
37
 
@@ -33,7 +41,7 @@ describe MotionWechat::API do
33
41
  req.message.mediaObject.videoUrl.should == "http://www.youtube.com/1"
34
42
  end
35
43
 
36
- MotionWechat::API.instance.send_video "http://www.youtube.com/1", \
44
+ @mv.send_video "http://www.youtube.com/1", \
37
45
  title: "title", description: "description"
38
46
  end
39
47
 
@@ -43,7 +51,7 @@ describe MotionWechat::API do
43
51
  req.message.mediaObject.musicUrl.should == "http://www.pandora.com/1"
44
52
  end
45
53
 
46
- MotionWechat::API.instance.send_music "http://www.pandora.com/1", \
54
+ @mv.send_music "http://www.pandora.com/1", \
47
55
  title: "title", description: "description"
48
56
  end
49
57
 
@@ -52,7 +60,7 @@ describe MotionWechat::API do
52
60
  req.message.mediaObject.should.be.kind_of(WXImageObject)
53
61
  end
54
62
 
55
- MotionWechat::API.instance.send_image NSData.dataWithContentsOfFile("dummy"), \
63
+ @mv.send_image NSData.dataWithContentsOfFile("dummy"), \
56
64
  title: "title", description: "description"
57
65
  end
58
66
 
@@ -62,7 +70,7 @@ describe MotionWechat::API do
62
70
  req.text.should == "hello"
63
71
  end
64
72
 
65
- MotionWechat::API.instance.send_text "hello"
73
+ @mv.send_text "hello"
66
74
  end
67
75
 
68
76
  describe "Scene type" do
@@ -73,7 +81,7 @@ describe MotionWechat::API do
73
81
  req.scene.should == WXSceneSession
74
82
  end
75
83
 
76
- MotionWechat::API.instance.send_webpage "http://www.motion-wechat.com", \
84
+ @mv.send_webpage "http://www.motion-wechat.com", \
77
85
  title: "title", description: "description"
78
86
  end
79
87
 
@@ -83,7 +91,7 @@ describe MotionWechat::API do
83
91
  req.scene.should == WXSceneTimeline
84
92
  end
85
93
 
86
- MotionWechat::API.instance.send_webpage "http://www.motion-wechat.com", \
94
+ @mv.send_webpage "http://www.motion-wechat.com", \
87
95
  title: "title", description: "description", scene: WXSceneTimeline
88
96
  end
89
97
 
@@ -0,0 +1,70 @@
1
+ describe MotionWechat::Client do
2
+ extend WebStub::SpecHelpers
3
+
4
+ before do
5
+ disable_network_access!
6
+ @client = MotionWechat::Client.new "appid", "appsecret", "code"
7
+ end
8
+
9
+ it "should get id, secret and site" do
10
+ @client.id.should == "appid"
11
+ @client.secret.should == "appsecret"
12
+ @client.site == "https://api.weixin.qq.com"
13
+ end
14
+
15
+ it "should get token url" do
16
+ @client.token_url.should == "https://api.weixin.qq.com/sns/oauth2/access_token"
17
+ end
18
+
19
+ context "access token" do
20
+
21
+ before do
22
+ @res = File.read NSBundle.mainBundle.resourcePath.stringByAppendingPathComponent("token.json")
23
+ @url = "https://api.weixin.qq.com/sns/oauth2/access_token" + \
24
+ "?appid=appid&secret=appsecret&code=code&grant_type=authorization_code"
25
+ end
26
+
27
+ it "gets token" do
28
+ stub_request(:get, @url).
29
+ to_return(json: @res)
30
+ @res = nil
31
+
32
+ @client.get_token do |token|
33
+ @token = token
34
+ resume
35
+ end
36
+
37
+ wait_max 1.0 do
38
+ @token.should.be.kind_of MotionWechat::AccessToken
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ context "get info" do
45
+
46
+ before do
47
+ @res = File.read NSBundle.mainBundle.resourcePath.stringByAppendingPathComponent("user.json")
48
+ @url = "https://api.weixin.qq.com/sns/user_info?access_token=token&openid=openid"
49
+ end
50
+
51
+ it "gets user info" do
52
+ stub_request(:get, @url).
53
+ to_return(json: @res)
54
+ @res = nil
55
+
56
+ @client.get("/sns/user_info", access_token: "token", openid: "openid") do |info|
57
+ @info = info
58
+ resume
59
+ end
60
+
61
+ wait_max 1.0 do
62
+ @info.should.be.kind_of Hash
63
+ @info["nickname"].should == "monkey"
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+
70
+ end
metadata CHANGED
@@ -1,27 +1,59 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-wechat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Qi He
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-11 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2014-09-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: afmotion
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 2.3.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 2.3.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bubble-wrap
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.7.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 1.7.0
13
41
  description: Rubymotion gem to easily use WeChatSDK
14
42
  email: qhe@heyook.com
15
43
  executables: []
16
44
  extensions: []
17
45
  extra_rdoc_files: []
18
46
  files:
47
+ - lib/motion-wechat/access_token.rb
19
48
  - lib/motion-wechat/api.rb
49
+ - lib/motion-wechat/client.rb
20
50
  - lib/motion-wechat/config.rb
21
51
  - lib/motion-wechat/version.rb
22
52
  - lib/motion-wechat.rb
23
53
  - README.md
54
+ - spec/motion-wechat/access_token_spec.rb
24
55
  - spec/motion-wechat/api_spec.rb
56
+ - spec/motion-wechat/client_spec.rb
25
57
  - spec/motion-wechat/config_spec.rb
26
58
  homepage: http://github.com/he9qi/motion-wechat
27
59
  licenses:
@@ -48,5 +80,7 @@ signing_key:
48
80
  specification_version: 4
49
81
  summary: Rubymotion wrapper for WeChatSDK
50
82
  test_files:
83
+ - spec/motion-wechat/access_token_spec.rb
51
84
  - spec/motion-wechat/api_spec.rb
85
+ - spec/motion-wechat/client_spec.rb
52
86
  - spec/motion-wechat/config_spec.rb