diff --git a/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js b/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js
new file mode 100644
index 00000000000..a534195a129
--- /dev/null
+++ b/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js
@@ -0,0 +1,52 @@
+/**
+  This controller supports the interface for granting and revoking badges from
+  individual users.
+
+  @class AdminUserBadgesController
+  @extends Ember.ArrayController
+  @namespace Discourse
+  @module Discourse
+**/
+Discourse.AdminUserBadgesController = Ember.ArrayController.extend({
+  needs: ["adminUser"],
+  user: Em.computed.alias('controllers.adminUser'),
+  sortProperties: ['granted_at'],
+  sortAscending: false,
+
+  actions: {
+
+    /**
+      Grant the selected badge to the user.
+
+      @method grantBadge
+      @param {Integer} badgeId id of the badge we want to grant.
+    **/
+    grantBadge: function(badgeId) {
+      var self = this;
+      Discourse.UserBadge.grant(badgeId, this.get('user.username')).then(function(userBadge) {
+        self.pushObject(userBadge);
+      }, function() {
+        // Failure
+        bootbox.alert(I18n.t('generic_error'));
+      });
+    },
+
+    /**
+      Revoke the selected userBadge.
+
+      @method revokeBadge
+      @param {Discourse.UserBadge} userBadge the `Discourse.UserBadge` instance that needs to be revoked.
+    **/
+    revokeBadge: function(userBadge) {
+      var self = this;
+      return bootbox.confirm(I18n.t("admin.badges.revoke_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
+        if (result) {
+          userBadge.revoke().then(function() {
+            self.get('model').removeObject(userBadge);
+          });
+        }
+      });
+    }
+
+  }
+});
diff --git a/app/assets/javascripts/admin/routes/admin_routes.js b/app/assets/javascripts/admin/routes/admin_routes.js
index 21b2b38b6a6..1143df781ce 100644
--- a/app/assets/javascripts/admin/routes/admin_routes.js
+++ b/app/assets/javascripts/admin/routes/admin_routes.js
@@ -47,6 +47,7 @@ Discourse.Route.buildRoutes(function() {
 
     this.resource('adminUsers', { path: '/users' }, function() {
       this.resource('adminUser', { path: '/:username' }, function() {
+        this.route('badges');
         this.route('leaderRequirements', { path: '/leader_requirements' });
       });
       this.resource('adminUsersList', { path: '/list' }, function() {
diff --git a/app/assets/javascripts/admin/routes/admin_user_badges_route.js b/app/assets/javascripts/admin/routes/admin_user_badges_route.js
new file mode 100644
index 00000000000..b1184b3b12e
--- /dev/null
+++ b/app/assets/javascripts/admin/routes/admin_user_badges_route.js
@@ -0,0 +1,31 @@
+/**
+  Shows all of the badges that have been granted to a user, and allow granting and
+  revoking badges.
+
+  @class AdminUserBadgesRoute
+  @extends Discourse.Route
+  @namespace Discourse
+  @module Discourse
+**/
+Discourse.AdminUserBadgesRoute = Discourse.Route.extend({
+  model: function() {
+    var username = this.controllerFor('adminUser').get('username');
+    return Discourse.UserBadge.findByUsername(username);
+  },
+
+  setupController: function(controller, model) {
+    // Find all badges.
+    controller.set('loading', true);
+    Discourse.Badge.findAll().then(function(badges) {
+      controller.set('badges', badges);
+      if (badges.length > 0) {
+        controller.set('selectedBadgeId', badges[0].get('id'));
+      } else {
+        controller.set('noBadges', true);
+      }
+      controller.set('loading', false);
+    });
+    // Set the model.
+    controller.set('model', model);
+  }
+});
diff --git a/app/assets/javascripts/admin/templates/user_badges.js.handlebars b/app/assets/javascripts/admin/templates/user_badges.js.handlebars
new file mode 100644
index 00000000000..aec5a82889f
--- /dev/null
+++ b/app/assets/javascripts/admin/templates/user_badges.js.handlebars
@@ -0,0 +1,61 @@
+<div class='admin-controls'>
+  <div class='span15'>
+    <ul class='nav nav-pills'>
+      <li>{{#link-to 'adminUser' user}}<i class="fa fa-caret-left"></i> &nbsp;{{user.username}}{{/link-to}}</li>
+    </ul>
+  </div>
+</div>
+
+{{#if loading}}
+  <div class='spinner'>{{i18n loading}}</div>
+{{else}}
+  <div class='admin-container user-badges'>
+    <h2>{{i18n admin.badges.grant_badge}}</h2>
+    {{#if noBadges}}
+      <p>{{i18n admin.badges.no_badges}}</p>
+    {{else}}
+      <br>
+      {{combobox valueAttribute="id" value=controller.selectedBadgeId content=controller.badges}}
+      <button class='btn btn-primary' {{action grantBadge controller.selectedBadgeId}}>{{i18n admin.badges.grant}}</button>
+    {{/if}}
+
+    <br>
+    <br>
+
+    <h2>{{i18n admin.badges.granted_badges}}</h2>
+    <br>
+
+    <table>
+      <tr>
+        <th>{{i18n admin.badges.name}}</th>
+        <th>{{i18n admin.badges.badge_type}}</th>
+        <th>{{i18n admin.badges.granted_by}}</th>
+        <th>{{i18n admin.badges.granted_at}}</th>
+        <th></th>
+      </tr>
+
+      {{#each}}
+        <tr>
+          <td>{{badge.displayName}}</td>
+          <td>{{badge.badge_type.name}}</td>
+          <td>
+            {{#link-to 'adminUser' badge.granted_by}}
+              {{avatar granted_by imageSize="tiny"}}
+              {{granted_by.username}}
+            {{/link-to}}
+          </td>
+          <td>{{unboundAgeWithTooltip granted_at}}</td>
+          <td>
+            <button class='btn' {{action revokeBadge this}}>{{i18n admin.badges.revoke}}</button>
+          </td>
+        </tr>
+      {{else}}
+        <tr>
+          <td colspan="5">
+            <p>{{i18n admin.badges.no_user_badges name=user.username}}</p>
+          </td>
+        </tr>
+      {{/each}}
+    </table>
+  </div>
+{{/if}}
diff --git a/app/assets/javascripts/admin/templates/user_index.js.handlebars b/app/assets/javascripts/admin/templates/user_index.js.handlebars
index 8af5cd2c5f3..53c731de8ef 100644
--- a/app/assets/javascripts/admin/templates/user_index.js.handlebars
+++ b/app/assets/javascripts/admin/templates/user_index.js.handlebars
@@ -77,6 +77,18 @@
     </div>
   </div>
 
+  {{#if showBadges}}
+    <div class='display-row'>
+      <div class='field'>{{i18n admin.badges.title}}</div>
+      <div class='value'>
+        TODO featured badges
+      </div>
+      <div class='controls'>
+        {{#link-to 'adminUser.badges' this class="btn"}}{{i18n admin.badges.edit_badges}}{{/link-to}}
+      </div>
+    </div>
+  {{/if}}
+
 </section>
 
 
@@ -336,12 +348,6 @@
   </div>
 </section>
 
-{{#if showBadges}}
-<section class='details'>
-  <h1>{{i18n admin.badges.title}}</h1>
-</section>
-{{/if}}
-
 <section>
   <hr/>
   <button {{bind-attr class=":btn :btn-danger :pull-right deleteForbidden:hidden"}} {{action destroy target="content"}} {{bind-attr disabled="deleteForbidden"}}>
diff --git a/app/assets/javascripts/discourse/models/user_badge.js b/app/assets/javascripts/discourse/models/user_badge.js
index f0f6595c831..3d521c1fa12 100644
--- a/app/assets/javascripts/discourse/models/user_badge.js
+++ b/app/assets/javascripts/discourse/models/user_badge.js
@@ -7,6 +7,17 @@
   @module Discourse
 **/
 Discourse.UserBadge = Discourse.Model.extend({
+  /**
+    Revoke this badge.
+
+    @method revoke
+    @returns {Promise} a promise that resolves when the badge has been revoked.
+  **/
+  revoke: function() {
+    return Discourse.ajax("/user_badges/" + this.get('id'), {
+      type: "DELETE"
+    });
+  }
 });
 
 Discourse.UserBadge.reopenClass({
@@ -19,14 +30,15 @@ Discourse.UserBadge.reopenClass({
   **/
   createFromJson: function(json) {
     // Create User objects.
+    if (json.users === undefined) { json.users = []; }
     var users = {};
     json.users.forEach(function(userJson) {
       users[userJson.id] = Discourse.User.create(userJson);
     });
 
     // Create the badges.
+    if (json.badges === undefined) { json.badges = []; }
     var badges = {};
-
     Discourse.Badge.createFromJson(json).forEach(function(badge) {
       badges[badge.get('id')] = badge;
     });
@@ -53,5 +65,37 @@ Discourse.UserBadge.reopenClass({
     } else {
       return userBadges;
     }
+  },
+
+  /**
+    Find all badges for a given username.
+
+    @method findByUsername
+    @returns {Promise} a promise that resolves to an array of `Discourse.UserBadge`.
+  **/
+  findByUsername: function(username) {
+    return Discourse.ajax("/user_badges.json?username=" + username).then(function(json) {
+      return Discourse.UserBadge.createFromJson(json);
+    });
+  },
+
+  /**
+    Grant the badge having id `badgeId` to the user identified by `username`.
+
+    @method grant
+    @param {Integer} badgeId id of the badge to be granted.
+    @param {String} username username of the user to be granted the badge.
+    @returns {Promise} a promise that resolves to an instance of `Discourse.UserBadge`.
+  **/
+  grant: function(badgeId, username) {
+    return Discourse.ajax("/user_badges", {
+      type: "POST",
+      data: {
+        username: username,
+        badge_id: badgeId
+      }
+    }).then(function(json) {
+      return Discourse.UserBadge.createFromJson(json);
+    });
   }
 });
diff --git a/app/assets/javascripts/discourse/views/combobox_view.js b/app/assets/javascripts/discourse/views/combobox_view.js
index 8469c0f707d..25472a4b3ff 100644
--- a/app/assets/javascripts/discourse/views/combobox_view.js
+++ b/app/assets/javascripts/discourse/views/combobox_view.js
@@ -42,7 +42,7 @@ Discourse.ComboboxView = Discourse.View.extend({
         if (val) { val = val.toString(); }
 
         var selectedText = (val === selected) ? "selected" : "";
-        buffer.push("<option " + selectedText + " value=\"" + val + "\" " + self.buildData(o) + ">" + Em.get(o, nameProperty) + "</option>");
+        buffer.push("<option " + selectedText + " value=\"" + val + "\" " + self.buildData(o) + ">" + Handlebars.Utils.escapeExpression(Em.get(o, nameProperty)) + "</option>");
       });
     }
   },
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index b7abad65b06..000fb7c2a63 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -169,6 +169,9 @@ class Admin::UsersController < Admin::AdminController
     end
   end
 
+  def badges
+  end
+
   def leader_requirements
   end
 
diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb
index 3044d7c1656..ef5bbf3345f 100644
--- a/app/controllers/user_badges_controller.rb
+++ b/app/controllers/user_badges_controller.rb
@@ -2,7 +2,7 @@ class UserBadgesController < ApplicationController
   def index
     params.require(:username)
     user = fetch_user_from_params
-    render json: user.user_badges
+    render_serialized(user.user_badges, UserBadgeSerializer, root: "user_badges")
   end
 
   def create
@@ -17,7 +17,7 @@ class UserBadgesController < ApplicationController
     badge = fetch_badge_from_params
     user_badge = BadgeGranter.grant(badge, user, granted_by: current_user)
 
-    render json: user_badge
+    render_serialized(user_badge, UserBadgeSerializer, root: "user_badge")
   end
 
   def destroy
@@ -29,7 +29,7 @@ class UserBadgesController < ApplicationController
       return
     end
 
-    BadgeGranter.revoke(user_badge)
+    BadgeGranter.revoke(user_badge, revoked_by: current_user)
     render json: success_json
   end
 
diff --git a/app/models/user_history.rb b/app/models/user_history.rb
index fccf90cc298..bc4033c2c6d 100644
--- a/app/models/user_history.rb
+++ b/app/models/user_history.rb
@@ -23,7 +23,9 @@ class UserHistory < ActiveRecord::Base
                            :notified_about_dominating_topic,
                            :suspend_user,
                            :unsuspend_user,
-                           :facebook_no_email)
+                           :facebook_no_email,
+                           :grant_badge,
+                           :revoke_badge)
   end
 
   # Staff actions is a subset of all actions, used to audit actions taken by staff users.
@@ -34,7 +36,9 @@ class UserHistory < ActiveRecord::Base
                         :change_site_customization,
                         :delete_site_customization,
                         :suspend_user,
-                        :unsuspend_user]
+                        :unsuspend_user,
+                        :grant_badge,
+                        :revoke_badge]
   end
 
   def self.staff_action_ids
diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb
index 2fb74972dbe..d164f7d3ca0 100644
--- a/app/services/badge_granter.rb
+++ b/app/services/badge_granter.rb
@@ -19,15 +19,21 @@ class BadgeGranter
                                      granted_by: @granted_by, granted_at: Time.now)
 
       Badge.increment_counter 'grant_count', @badge.id
+      if @granted_by != Discourse.system_user
+        StaffActionLogger.new(@granted_by).log_badge_grant(user_badge)
+      end
     end
 
     user_badge
   end
 
-  def self.revoke(user_badge)
+  def self.revoke(user_badge, options={})
     UserBadge.transaction do
       user_badge.destroy!
       Badge.decrement_counter 'grant_count', user_badge.badge.id
+      if options[:revoked_by]
+        StaffActionLogger.new(options[:revoked_by]).log_badge_revoke(user_badge)
+      end
     end
   end
 
diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb
index bb9fa1f7401..b92deba695e 100644
--- a/app/services/staff_action_logger.rb
+++ b/app/services/staff_action_logger.rb
@@ -74,6 +74,24 @@ class StaffActionLogger
     }))
   end
 
+  def log_badge_grant(user_badge, opts={})
+    raise Discourse::InvalidParameters.new('user_badge is nil') unless user_badge
+    UserHistory.create( params(opts).merge({
+      action: UserHistory.actions[:grant_badge],
+      target_user_id: user_badge.user_id,
+      details: user_badge.badge.name
+    }))
+  end
+
+  def log_badge_revoke(user_badge, opts={})
+    raise Discourse::InvalidParameters.new('user_badge is nil') unless user_badge
+    UserHistory.create( params(opts).merge({
+      action: UserHistory.actions[:revoke_badge],
+      target_user_id: user_badge.user_id,
+      details: user_badge.badge.name
+    }))
+  end
+
   private
 
   def params(opts)
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index c13503d0c84..c43de385f67 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1484,6 +1484,8 @@ en:
             delete_site_customization: "delete site customization"
             suspend_user: "suspend user"
             unsuspend_user: "unsuspend user"
+            grant_badge: "grant badge"
+            revoke_badge: "revoke badge"
         screened_emails:
           title: "Screened Emails"
           description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
@@ -1678,9 +1680,19 @@ en:
         display_name: Display Name
         description: Description
         badge_type: Badge Type
+        granted_by: Granted By
+        granted_at: Granted At
         save: Save
         delete: Delete
         delete_confirm: Are you sure you want to delete this badge?
+        revoke: Revoke
+        revoke_confirm: Are you sure you want to revoke this badge?
+        edit_badges: Edit Badges
+        grant_badge: Grant Badge
+        granted_badges: Granted Badges
+        grant: Grant
+        no_user_badges: "%{name} has not been granted any badges."
+        no_badges: There are no badges that can be granted.
 
     lightbox:
       download: "download"
diff --git a/config/routes.rb b/config/routes.rb
index 40b9c6181d5..6e28b6b5cf8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -61,6 +61,7 @@ Discourse::Application.routes.draw do
       put "unblock"
       put "trust_level"
       put "primary_group"
+      get "badges"
       get "leader_requirements"
     end
 
diff --git a/spec/controllers/user_badges_controller_spec.rb b/spec/controllers/user_badges_controller_spec.rb
index 4836df3ffd8..9d3fdbf1f43 100644
--- a/spec/controllers/user_badges_controller_spec.rb
+++ b/spec/controllers/user_badges_controller_spec.rb
@@ -36,6 +36,7 @@ describe UserBadgesController do
     it 'grants badges from staff' do
       admin = Fabricate(:admin)
       log_in_user admin
+      StaffActionLogger.any_instance.expects(:log_badge_grant).once
       xhr :post, :create, badge_id: badge.id, username: user.username
       response.status.should == 200
       user_badge = UserBadge.where(user: user, badge: badge).first
@@ -51,6 +52,7 @@ describe UserBadgesController do
 
     it 'grants badges from master api calls' do
       api_key = Fabricate(:api_key)
+      StaffActionLogger.any_instance.expects(:log_badge_grant).never
       xhr :post, :create, badge_id: badge.id, username: user.username, api_key: api_key.key
       response.status.should == 200
       user_badge = UserBadge.where(user: user, badge: badge).first
@@ -71,6 +73,7 @@ describe UserBadgesController do
 
     it 'revokes the badge' do
       log_in :admin
+      StaffActionLogger.any_instance.expects(:log_badge_revoke).once
       xhr :delete, :destroy, id: @user_badge.id
       response.status.should == 200
       UserBadge.where(id: @user_badge.id).first.should be_nil
diff --git a/spec/services/badge_granter_spec.rb b/spec/services/badge_granter_spec.rb
index 1ffcde77ffc..6801bc969d1 100644
--- a/spec/services/badge_granter_spec.rb
+++ b/spec/services/badge_granter_spec.rb
@@ -24,11 +24,13 @@ describe BadgeGranter do
 
     it 'sets granted_by if the option is present' do
       admin = Fabricate(:admin)
+      StaffActionLogger.any_instance.expects(:log_badge_grant).once
       user_badge = BadgeGranter.grant(badge, user, granted_by: admin)
       user_badge.granted_by.should eq(admin)
     end
 
     it 'defaults granted_by to the system user' do
+      StaffActionLogger.any_instance.expects(:log_badge_grant).never
       user_badge = BadgeGranter.grant(badge, user)
       user_badge.granted_by_id.should eq(Discourse.system_user.id)
     end
@@ -47,11 +49,13 @@ describe BadgeGranter do
 
   describe 'revoke' do
 
+    let(:admin) { Fabricate(:admin) }
     let!(:user_badge) { BadgeGranter.grant(badge, user) }
 
     it 'revokes the badge and decrements grant_count' do
       badge.reload.grant_count.should eq(1)
-      BadgeGranter.revoke(user_badge)
+      StaffActionLogger.any_instance.expects(:log_badge_revoke).with(user_badge)
+      BadgeGranter.revoke(user_badge, revoked_by: admin)
       UserBadge.where(user: user, badge: badge).first.should_not be_present
       badge.reload.grant_count.should eq(0)
     end
diff --git a/spec/services/staff_action_logger_spec.rb b/spec/services/staff_action_logger_spec.rb
index 4ce04aabd66..b0466c022f3 100644
--- a/spec/services/staff_action_logger_spec.rb
+++ b/spec/services/staff_action_logger_spec.rb
@@ -150,4 +150,38 @@ describe StaffActionLogger do
       log_record.target_user.should == user
     end
   end
+
+  describe "log_badge_grant" do
+    let(:user) { Fabricate(:user) }
+    let(:badge) { Fabricate(:badge) }
+    let(:user_badge) { BadgeGranter.grant(badge, user) }
+
+    it "raises an error when argument is missing" do
+      expect { logger.log_badge_grant(nil) }.to raise_error(Discourse::InvalidParameters)
+    end
+
+    it "creates a new UserHistory record" do
+      log_record = logger.log_badge_grant(user_badge)
+      log_record.should be_valid
+      log_record.target_user.should == user
+      log_record.details.should == badge.name
+    end
+  end
+
+  describe "log_badge_revoke" do
+    let(:user) { Fabricate(:user) }
+    let(:badge) { Fabricate(:badge) }
+    let(:user_badge) { BadgeGranter.grant(badge, user) }
+
+    it "raises an error when argument is missing" do
+      expect { logger.log_badge_revoke(nil) }.to raise_error(Discourse::InvalidParameters)
+    end
+
+    it "creates a new UserHistory record" do
+      log_record = logger.log_badge_revoke(user_badge)
+      log_record.should be_valid
+      log_record.target_user.should == user
+      log_record.details.should == badge.name
+    end
+  end
 end
diff --git a/test/javascripts/models/user_badge_test.js b/test/javascripts/models/user_badge_test.js
index 306549ad68f..eb56100df94 100644
--- a/test/javascripts/models/user_badge_test.js
+++ b/test/javascripts/models/user_badge_test.js
@@ -1,9 +1,10 @@
 module("Discourse.UserBadge");
 
-test('createFromJson single', function() {
-  var json = {"badges":[{"id":874,"name":"Badge 2","description":null,"badge_type_id":7}],"badge_types":[{"id":7,"name":"Silver 2","color_hexcode":"#c0c0c0"}],"users":[{"id":13470,"username":"anne3","avatar_template":"//www.gravatar.com/avatar/a4151b1fd72089c54e2374565a87da7f.png?s={size}\u0026r=pg\u0026d=identicon"}],"user_badge":{"id":665,"granted_at":"2014-03-09T20:30:01.190-04:00","badge_id":874,"granted_by_id":13470}};
+var singleBadgeJson = {"badges":[{"id":874,"name":"Badge 2","description":null,"badge_type_id":7}],"badge_types":[{"id":7,"name":"Silver 2","color_hexcode":"#c0c0c0"}],"users":[{"id":13470,"username":"anne3","avatar_template":"//www.gravatar.com/avatar/a4151b1fd72089c54e2374565a87da7f.png?s={size}\u0026r=pg\u0026d=identicon"}],"user_badge":{"id":665,"granted_at":"2014-03-09T20:30:01.190-04:00","badge_id":874,"granted_by_id":13470}},
+    multipleBadgesJson = {"badges":[{"id":880,"name":"Badge 8","description":null,"badge_type_id":13}],"badge_types":[{"id":13,"name":"Silver 8","color_hexcode":"#c0c0c0"}],"users":[],"user_badges":[{"id":668,"granted_at":"2014-03-09T20:30:01.420-04:00","badge_id":880,"granted_by_id":null}]};
 
-  var userBadge = Discourse.UserBadge.createFromJson(json);
+test('createFromJson single', function() {
+  var userBadge = Discourse.UserBadge.createFromJson(singleBadgeJson);
   ok(!Array.isArray(userBadge), "does not return an array");
   equal(userBadge.get('badge.name'), "Badge 2", "badge reference is set");
   equal(userBadge.get('badge.badge_type.name'), "Silver 2", "badge.badge_type reference is set");
@@ -11,9 +12,30 @@ test('createFromJson single', function() {
 });
 
 test('createFromJson array', function() {
-  var json = {"badges":[{"id":880,"name":"Badge 8","description":null,"badge_type_id":13}],"badge_types":[{"id":13,"name":"Silver 8","color_hexcode":"#c0c0c0"}],"users":[],"user_badges":[{"id":668,"granted_at":"2014-03-09T20:30:01.420-04:00","badge_id":880,"granted_by_id":null}]};
-
-  var userBadges = Discourse.UserBadge.createFromJson(json);
+  var userBadges = Discourse.UserBadge.createFromJson(multipleBadgesJson);
   ok(Array.isArray(userBadges), "returns an array");
   equal(userBadges[0].get('granted_by'), null, "granted_by reference is not set when null");
 });
+
+test('findByUsername', function() {
+  this.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve(multipleBadgesJson));
+  Discourse.UserBadge.findByUsername("anne3").then(function(badges) {
+    ok(Array.isArray(badges), "returns an array");
+  });
+  ok(Discourse.ajax.calledOnce, "makes an AJAX call");
+});
+
+test('grant', function() {
+  this.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve(singleBadgeJson));
+  Discourse.UserBadge.grant(1, "username").then(function(userBadge) {
+    ok(!Array.isArray(userBadge), "does not return an array");
+  });
+  ok(Discourse.ajax.calledOnce, "makes an AJAX call");
+});
+
+test('revoke', function() {
+  this.stub(Discourse, 'ajax');
+  var userBadge = Discourse.UserBadge.create({id: 1});
+  userBadge.revoke();
+  ok(Discourse.ajax.calledOnce, "makes an AJAX call");
+});