#277 Re-institute admin functionality for WebGoat 8

- Report card functionality is back
This commit is contained in:
Nanne Baars 2016-12-31 18:27:20 +01:00
parent 490f542885
commit e2cb9ceae0
14 changed files with 457 additions and 135 deletions

View File

@ -0,0 +1,111 @@
/**
* *************************************************************************************************
* <p>
* <p>
* This file is part of WebGoat, an Open Web Application Security Project
* utility. For details, please see http://www.owasp.org/
* <p>
* Copyright (c) 2002 - 20014 Bruce Mayhew
* <p>
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
* <p>
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307, USA.
* <p>
* Getting Source ==============
* <p>
* Source for this application is maintained at
* https://github.com/WebGoat/WebGoat, a repository for free software projects.
*/
package org.owasp.webgoat.service;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Getter;
import lombok.Setter;
import lombok.Singular;
import org.apache.catalina.User;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.LessonTracker;
import org.owasp.webgoat.session.UserTracker;
import org.owasp.webgoat.session.WebSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>ReportCardService</p>
*
* @author nbaars
* @version $Id: $Id
*/
@Controller
public class ReportCardService {
private final UserTracker userTracker;
private final Course course;
public ReportCardService(UserTracker userTracker, Course course) {
this.userTracker = userTracker;
this.course = course;
}
/**
* Endpoint which generates the report card for the current use to show the stats on the solved lessons
*/
@GetMapping(path = "/service/reportcard.mvc", produces = "application/json")
@ResponseBody
public ReportCard reportCard() {
List<AbstractLesson> lessons = course.getLessons();
ReportCard reportCard = new ReportCard();
reportCard.setTotalNumberOfLessons(course.getTotalOfLessons());
reportCard.setTotalNumberOfAssignments(course.getTotalOfAssignments());
reportCard.setNumberOfAssignmentsSolved(userTracker.numberOfAssignmentsSolved());
reportCard.setNumberOfLessonsSolved(userTracker.numberOfLessonsSolved());
for (AbstractLesson lesson : lessons) {
LessonTracker lessonTracker = userTracker.getLessonTracker(lesson);
LessonStatistics lessonStatistics = new LessonStatistics();
lessonStatistics.setName(lesson.getTitle());
lessonStatistics.setNumberOfAttempts(lessonTracker.getNumberOfAttempts());
lessonStatistics.setSolved(lessonTracker.isLessonSolved());
reportCard.lessonStatistics.add(lessonStatistics);
}
return reportCard;
}
@Getter
@Setter
private class ReportCard {
private int totalNumberOfLessons;
private int totalNumberOfAssignments;
private int solvedLessons;
private int numberOfAssignmentsSolved;
private int numberOfLessonsSolved;
private List<LessonStatistics> lessonStatistics = Lists.newArrayList();
}
@Setter
@Getter
private class LessonStatistics {
private String name;
private boolean solved;
private int numberOfAttempts;
}
}

View File

@ -89,5 +89,14 @@ public class Course {
this.lessons = lessons; this.lessons = lessons;
} }
public int getTotalOfLessons() {
return this.lessons.size();
}
public int getTotalOfAssignments() {
final int[] total = {0};
this.lessons.stream().forEach(l -> total[0] = total[0] + l.getAssignments().size());
return total[0];
}
} }

View File

@ -4,12 +4,14 @@ package org.owasp.webgoat.session;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.owasp.webgoat.lessons.AbstractLesson; import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.lessons.Assignment;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import org.springframework.util.SerializationUtils; import org.springframework.util.SerializationUtils;
import java.io.File; import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
/** /**
@ -104,4 +106,23 @@ public class UserTracker {
getLessonTracker(al).reset(); getLessonTracker(al).reset();
save(); save();
} }
public int numberOfLessonsSolved() {
int numberOfLessonsSolved = 0;
for(LessonTracker lessonTracker : storage.values()) {
if (lessonTracker.isLessonSolved()) {
numberOfLessonsSolved = numberOfLessonsSolved + 1;
}
}
return numberOfLessonsSolved;
}
public int numberOfAssignmentsSolved() {
int numberOfAssignmentsSolved = 0;
for (LessonTracker lessonTracker : storage.values()) {
Map<Assignment, Boolean> lessonOverview = lessonTracker.getLessonOverview();
numberOfAssignmentsSolved = lessonOverview.values().stream().filter(b -> b).collect(Collectors.counting()).intValue();
}
return numberOfAssignmentsSolved;
}
} }

View File

@ -16,7 +16,7 @@ spring.devtools.restart.enabled=false
spring.resources.cache-period=0 spring.resources.cache-period=0
webgoat.tracker.overwrite=true webgoat.tracker.overwrite=false
webgoat.user.directory=${user.home}/.webgoat/ webgoat.user.directory=${user.home}/.webgoat/
webgoat.build.version=@project.version@ webgoat.build.version=@project.version@
webgoat.build.number=@build.number@ webgoat.build.number=@build.number@

View File

@ -55,9 +55,9 @@ define(['jquery',
this.lessonOverviewModel = new LessonOverviewModel(); this.lessonOverviewModel = new LessonOverviewModel();
this.lessonOverview = new LessonOverviewView(this.lessonOverviewModel); this.lessonOverview = new LessonOverviewView(this.lessonOverviewModel);
this.lessonContentView = options.lessonContentView; this.lessonContentView = options.lessonContentView;
this.titleView = options.titleView;
this.developerControlsView = new DeveloperControlsView(); this.developerControlsView = new DeveloperControlsView();
_.extend(Controller.prototype,Backbone.Events); _.extend(Controller.prototype,Backbone.Events);
this.start = function() { this.start = function() {
@ -67,12 +67,13 @@ define(['jquery',
}; };
this.loadLesson = function(name,pageNum) { this.loadLesson = function(name,pageNum) {
if (this.name === name) { if (this.name === name) {
this.lessonContentView.navToPage(pageNum) this.lessonContentView.navToPage(pageNum);
this.titleView.render(this.lessonInfoModel.get('lessonTitle'));
return; return;
} }
this.titleView = new TitleView();
this.helpsLoaded = {}; this.helpsLoaded = {};
if (typeof(name) === 'undefined' || name === null) { if (typeof(name) === 'undefined' || name === null) {
//TODO: implement lesson not found or return to welcome page? //TODO: implement lesson not found or return to welcome page?

View File

@ -11,22 +11,9 @@ define(['jquery',
_.extend(Controller.prototype,Backbone.Events); _.extend(Controller.prototype,Backbone.Events);
options = options || {}; options = options || {};
this.menuView = options.menuView; this.menuView = options.menuView;
this.titleView = options.titleView;
// this.initMenu = function() {
// this.listenTo(this.menuView,'lesson:click',this.renderTitle);
// }
this.updateMenu = function(){ this.updateMenu = function(){
this.menuView.updateMenu(); this.menuView.updateMenu();
},
//TODO: move title rendering into lessonContent/View pipeline once data can support it
this.renderTitle = function(title) {
this.titleView.render(title);
} }
}; };
return Controller; return Controller;

View File

@ -0,0 +1,8 @@
define([
'backbone'],
function(
Backbone) {
return Backbone.Model.extend({
url: 'service/reportcard.mvc'
});
});

View File

@ -0,0 +1,47 @@
<div class="panel panel-default" style="margin-top:25px">
<div class="panel-heading alt"><b>Overview</b></div>
<table class="table">
<tbody>
<tr>
<td width="30%">Total number of lessons</td>
<td width="70%"><%= totalNumberOfLessons %></td>
</tr>
<tr>
<td width="30%">Total number of lessons solved</td>
<td width="70%"><%= numberOfLessonsSolved %></td>
</tr>
<tr>
<td width="30%">Total number of assignments</td>
<td width="70%"><%= totalNumberOfAssignments %></td>
</tr>
<tr>
<td width="30%">Total number of assignments solved</td>
<td width="70%"><%= numberOfAssignmentsSolved %></td>
</tr>
</tbody>
</table>
</div>
<div class="panel panel-default" style="margin-top:25px">
<div class="panel-heading"><b>Lesson overview</b></div>
<table class="table">
<thead>
<tr>
<th>Lesson name</th>
<th>Solved</th>
<th>Number of attempts</th>
</tr>
</thead>
<tbody>
<% _(lessonStatistics).each(function(lesson) { %>
<%= lesson.solved ? '<tr class="success">' : '<tr>' %>
<td><%= lesson.name %></td>
<td><%= lesson.solved %></td>
<td><%= lesson.numberOfAttempts %></td>
</tr>
<% }) %>
</tbody>
</table>
</div>

View File

@ -5,37 +5,56 @@ define(['jquery',
'goatApp/controller/MenuController', 'goatApp/controller/MenuController',
'goatApp/view/LessonContentView', 'goatApp/view/LessonContentView',
'goatApp/view/MenuView', 'goatApp/view/MenuView',
'goatApp/view/DeveloperControlsView' 'goatApp/view/DeveloperControlsView',
], function ($, 'goatApp/view/TitleView'
_, ], function ($,
Backbone, _,
LessonController, Backbone,
MenuController, LessonController,
LessonContentView, MenuController,
MenuView, LessonContentView,
DeveloperControlsView) { MenuView,
DeveloperControlsView,
TitleView) {
var lessonContentView = new LessonContentView(); var lessonContentView = new LessonContentView();
var menuView = new MenuView(); var menuView = new MenuView();
var developerControlsView = new DeveloperControlsView(); var developerControlsView = new DeveloperControlsView();
var titleView = new TitleView();
function getContentElement() {
return $('#main-content');
};
function render(view) {
$('div.pages').hide();
//TODO this works for now because we only have one page we should rewrite this a bit
if (view != null) {
$('#report-card-page').show();
} else {
$('#lesson-title').show();
$('#lesson-page').show();
}
};
var GoatAppRouter = Backbone.Router.extend({ var GoatAppRouter = Backbone.Router.extend({
routes: { routes: {
'welcome':'welcomeRoute', 'welcome': 'welcomeRoute',
'lesson/:name':'lessonRoute', 'lesson/:name': 'lessonRoute',
'lesson/:name/:pageNum':'lessonPageRoute', 'lesson/:name/:pageNum': 'lessonPageRoute',
'test/:param':'testRoute' 'test/:param': 'testRoute',
'reportCard': 'reportCard'
}, },
lessonController: new LessonController({ lessonController: new LessonController({
lessonContentView: lessonContentView lessonContentView: lessonContentView,
titleView: titleView
}), }),
menuController: new MenuController({ menuController: new MenuController({
menuView: menuView menuView: menuView
}), }),
setUpCustomJS: function () { setUpCustomJS: function () {
webgoat.customjs.jquery = $; //passing jquery into custom js scope ... still klunky, but works for now webgoat.customjs.jquery = $; //passing jquery into custom js scope ... still klunky, but works for now
@ -45,19 +64,19 @@ define(['jquery',
console.log(arguments.callee); console.log(arguments.callee);
// //
webgoat.customjs.jquery.ajax({ webgoat.customjs.jquery.ajax({
method:"POST", method: "POST",
url:"/WebGoat/CrossSiteScripting/dom-xss", url: "/WebGoat/CrossSiteScripting/dom-xss",
data:{param1:42,param2:24}, data: {param1: 42, param2: 24},
headers:{ headers: {
"webgoat-requested-by":"dom-xss-vuln" "webgoat-requested-by": "dom-xss-vuln"
}, },
contentType:'application/x-www-form-urlencoded; charset=UTF-8' contentType: 'application/x-www-form-urlencoded; charset=UTF-8'
}); });
} }
}, },
init:function() { init: function () {
goatRouter = new GoatAppRouter(); goatRouter = new GoatAppRouter();
this.lessonController.start(); this.lessonController.start();
// this.menuController.initMenu(); // this.menuController.initMenu();
webgoat = {}; webgoat = {};
@ -65,39 +84,48 @@ define(['jquery',
this.setUpCustomJS(); this.setUpCustomJS();
goatRouter.on('route:lessonRoute', function (name) {
goatRouter.on('route:lessonRoute', function(name) { render();
this.lessonController.loadLesson(name,0); this.lessonController.loadLesson(name, 0);
//TODO - update menu code from below //TODO - update menu code from below
this.menuController.updateMenu(name); this.menuController.updateMenu(name);
}); });
goatRouter.on('route:lessonPageRoute', function(name,pageNum) { goatRouter.on('route:lessonPageRoute', function (name, pageNum) {
render();
pageNum = (_.isNumber(parseInt(pageNum))) ? parseInt(pageNum) : 0; pageNum = (_.isNumber(parseInt(pageNum))) ? parseInt(pageNum) : 0;
this.lessonController.loadLesson(name,pageNum); this.lessonController.loadLesson(name, pageNum);
//TODO - update menu code from below //TODO - update menu code from below
this.menuController.updateMenu(name); this.menuController.updateMenu(name);
}); });
goatRouter.on('route:welcomeRoute', function() { goatRouter.on('route:welcomeRoute', function () {
render();
this.lessonController.loadWelcome(); this.lessonController.loadWelcome();
}); });
goatRouter.on('route:testRoute', function(param) { goatRouter.on('route:testRoute', function (param) {
render();
this.lessonController.testHandler(param); this.lessonController.testHandler(param);
}); });
goatRouter.on("route", function(route, params) {}); goatRouter.on("route", function (route, params) {
});
Backbone.history.start(); Backbone.history.start();
this.listenTo(this.lessonController, 'menu:reload',this.reloadMenu) this.listenTo(this.lessonController, 'menu:reload', this.reloadMenu)
}, },
reloadMenu: function (curLesson) { reloadMenu: function (curLesson) {
this.menuController.updateMenu(); this.menuController.updateMenu();
} },
reportCard : function () {
require(['goatApp/view/ReportCardView'], function (ReportCardView) {
titleView.render('Report card');
render(new ReportCardView());
});
},
}); });
return GoatAppRouter; return GoatAppRouter;

View File

@ -0,0 +1,21 @@
define(['jquery', 'backbone', 'underscore', 'goatApp/model/ReportCardModel', 'text!templates/report_card.html'],
function ($, Backbone, _, ReportCardModel, ReportCardTemplate) {
return Backbone.View.extend({
el: '#report-card-page',
template: ReportCardTemplate,
initialize: function () {
var _this = this;
this.model = new ReportCardModel();
this.model.fetch().then(function() {
_this.render();
});
},
render: function () {
var t = _.template(this.template);
this.$el.html(t(this.model.toJSON()));
return this;
}
});
});

View File

@ -16,9 +16,9 @@ require.config({
jquery: 'libs/jquery-1.10.2.min', jquery: 'libs/jquery-1.10.2.min',
underscore: 'libs/underscore-min', underscore: 'libs/underscore-min',
backbone: 'libs/backbone-min', backbone: 'libs/backbone-min',
templates: 'goatApp/templates' text: 'libs/text',
} templates: 'goatApp/templates',
, },
shim: { shim: {
underscore: { underscore: {
exports: "_" exports: "_"

View File

@ -90,6 +90,19 @@
</ul> </ul>
</div> </div>
<div style="display:inline" id="settings">
<!--<button type="button" id="admin-button" class="btn btn-default right_nav_button" title="Administrator">-->
<!--<i class="fa fa-cog"></i>-->
<!--</button>-->
<button type="button" id="report-card-button" class="btn btn-default right_nav_button button-up"
title="Report card">
<a href="#reportCard"><i class="fa fa-bar-chart-o"></i></a>
</button>
<!--<button type="button" id="user-management" class="btn btn-default right_nav_button"-->
<!--title="User management">-->
<!--<i class="fa fa-users"></i>-->
<!--</button>-->
</div>
<button type="button" id="about-button" class="btn btn-default right_nav_button" title="About WebGoat" <button type="button" id="about-button" class="btn btn-default right_nav_button" title="About WebGoat"
data-toggle="modal" data-target="#about-modal"> data-toggle="modal" data-target="#about-modal">
<i class="fa fa-info"></i> <i class="fa fa-info"></i>
@ -112,98 +125,103 @@
<!--main content start--> <!--main content start-->
<section class="main-content-wrapper"> <section class="main-content-wrapper">
<section id="main-content"> <!--ng-controller="goatLesson"--> <section id="main-content"> <!--ng-controller="goatLesson"-->
<div class="row"> <div id="lesson-page" class="pages">
<div class="col-md-8"> <div class="row">
<!--<div class="col-md-12" align="left">--> <div class="col-md-8">
<!--<div class="col-md-12" align="left">-->
<!----> <!---->
<!--&lt;!&ndash; hints moved into lesson template &ndash;&gt;--> <!--&lt;!&ndash; hints moved into lesson template &ndash;&gt;-->
<!--</div>--> <!--</div>-->
<div class="col-md-12" align="left"> <div class="col-md-12" align="left">
<div id="lesson-content-wrapper" class="panel"> <div id="lesson-content-wrapper" class="panel">
<div class="" id="error-notification-container"> <div class="" id="error-notification-container">
<div class="" id="error-notification"> <div class="" id="error-notification">
<i class="fa fa-exclamation-circle" /> There was an unexpected error. Please try again. <i class="fa fa-exclamation-circle"/> There was an unexpected error. Please try
</div> again.
</div>
<div class="" id="help-controls">
<button class="btn btn-primary btn-xs btn-danger help-button" id="show-source-button">
<i class="fa fa-code" />
</button>
<button class="btn btn-primary btn-xs btn-danger help-button" id="show-hints-button">Show Hints
</button>
<!--<button class="btn btn-primary btn-xs btn-danger help-button" id="show-attack-button">-->
<!--Attack It-->
<!--</button>-->
<button class="btn btn-primary btn-xs btn-danger help-button" id="show-lesson-overview-button">Lesson overview
</button>
<button class="btn btn-xs help-button" id="restart-lesson-button">
Reset Lesson
</button>
</div>
<div class="lesson-hint" id="lesson-hint-container">
<!--<h4>Hints</h4>-->
<div class="panel">
<div id="message" class="info" th:utext="${message}"></div>
<div class="panel-body" id="lesson-hint">
<span class="glyphicon-class glyphicon glyphicon-circle-arrow-left"
id="show-prev-hint"></span>
<span class="glyphicon-class glyphicon glyphicon-circle-arrow-right"
id="show-next-hint"></span>
<br/>
<span id="lesson-hint-content"></span>
</div> </div>
</div> </div>
</div> <div class="" id="help-controls">
<button class="btn btn-primary btn-xs btn-danger help-button"
id="show-source-button">
<i class="fa fa-code"/>
</button>
<button class="btn btn-primary btn-xs btn-danger help-button"
id="show-hints-button">Show Hints
</button>
<!--<button class="btn btn-primary btn-xs btn-danger help-button" id="show-attack-button">-->
<!--Attack It-->
<!--</button>-->
<button class="btn btn-primary btn-xs btn-danger help-button"
id="show-lesson-overview-button">Lesson overview
</button>
<button class="btn btn-xs help-button" id="restart-lesson-button">
Reset Lesson
</button>
</div>
<div class="lesson-hint" id="lesson-hint-container">
<!--<h4>Hints</h4>-->
<div class="panel">
<div id="message" class="info" th:utext="${message}"></div>
<div class="panel-body" id="lesson-hint">
<span class="glyphicon-class glyphicon glyphicon-circle-arrow-left"
id="show-prev-hint"></span>
<span class="glyphicon-class glyphicon glyphicon-circle-arrow-right"
id="show-next-hint"></span>
<br/>
<span id="lesson-hint-content"></span>
</div>
</div>
</div>
<div class="lesson-hint" id="lesson-overview-container">
<div class="panel">
<div class="panel-body" id="lesson-overview"></div>
</div>
</div>
<div class="lesson-content">
<div class="lesson-hint" id="lesson-overview-container">
<div class="panel">
<div class="panel-body" id="lesson-overview"></div>
</div> </div>
</div> </div>
</div>
</div>
</div>
<div id="lesson-helps-wrapper" class="panel">
<div class="lesson-help" id="lesson-plan-row">
<div class="col-md-12">
<h4>Lesson Plan</h4>
<div class="lesson-content"> <div class="panel">
<div class="panel-body" id="lesson-plan-content">
<!-- allowing jQuery to handle this one -->
</div>
</div>
</div>
</div>
<div class="lesson-help" id="lesson-solution-row">
<div class="col-md-12">
<h4>Lesson Solution</h4>
<div class="panel">
<div class="panel-body" id="lesson-solution-content">
</div>
</div>
</div>
</div>
<div class="lesson-help" id="lesson-source-row">
<div class="col-md-12">
<h4>Lesson Source Code</h4>
<div class="panel">
<div class="panel-body" id="lesson-source-content">
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div id="lesson-helps-wrapper" class="panel"> <div id="report-card-page" class="pages" style="display: none;">
<div class="lesson-help" id="lesson-plan-row">
<div class="col-md-12">
<h4>Lesson Plan</h4>
<div class="panel">
<div class="panel-body" id="lesson-plan-content">
<!-- allowing jQuery to handle this one -->
</div>
</div>
</div>
</div>
<div class="lesson-help" id="lesson-solution-row">
<div class="col-md-12">
<h4>Lesson Solution</h4>
<div class="panel">
<div class="panel-body" id="lesson-solution-content">
</div>
</div>
</div>
</div>
<div class="lesson-help" id="lesson-source-row">
<div class="col-md-12">
<h4>Lesson Source Code</h4>
<div class="panel">
<div class="panel-body" id="lesson-source-content">
</div>
</div>
</div>
</div>
</div> </div>
</section> </section>
</section> </section>

View File

@ -0,0 +1,59 @@
package org.owasp.webgoat.service;
import com.beust.jcommander.internal.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.owasp.webgoat.lessons.AbstractLesson;
import org.owasp.webgoat.session.Course;
import org.owasp.webgoat.session.LessonTracker;
import org.owasp.webgoat.session.UserTracker;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
@RunWith(MockitoJUnitRunner.class)
public class ReportCardServiceTest {
private MockMvc mockMvc;
@Mock
private Course course;
@Mock
private UserTracker userTracker;
@Mock
private AbstractLesson lesson;
@Mock
private LessonTracker lessonTracker;
@Before
public void setup() {
this.mockMvc = standaloneSetup(new ReportCardService(userTracker, course)).build();
}
@Test
@WithMockUser(username = "guest", password = "guest")
public void withLessons() throws Exception {
when(lesson.getTitle()).thenReturn("Test");
when(course.getTotalOfLessons()).thenReturn(1);
when(course.getTotalOfAssignments()).thenReturn(10);
when(course.getLessons()).thenReturn(Lists.newArrayList(lesson));
when(userTracker.getLessonTracker(any())).thenReturn(lessonTracker);
mockMvc.perform(MockMvcRequestBuilders.get("/service/reportcard.mvc"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalNumberOfLessons", is(1)))
.andExpect(jsonPath("$.solvedLessons", is(0)))
.andExpect(jsonPath("$.numberOfAssignmentsSolved", is(0)))
.andExpect(jsonPath("$.totalNumberOfAssignments", is(10)))
.andExpect(jsonPath("$.lessonStatistics[0].name", is("Test")))
.andExpect(jsonPath("$.numberOfAssignmentsSolved", is(0)));
}
}

View File

@ -10,6 +10,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -88,4 +89,15 @@ public class UserTrackerTest {
userTracker.reset(lesson); userTracker.reset(lesson);
assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isFalse(); assertThat(userTracker.getLessonTracker(lesson).isLessonSolved()).isFalse();
} }
@Test
public void totalAssignmentsSolved() {
UserTracker userTracker = new UserTracker(home.getParent(), "test", false);
AbstractLesson lesson = mock(AbstractLesson.class);
when(lesson.getAssignments()).thenReturn(Lists.newArrayList(new Assignment("assignment", "assignment")));
userTracker.assignmentSolved(lesson, "assignment");
assertThat(userTracker.numberOfAssignmentsSolved()).isEqualTo(1);
assertThat(userTracker.numberOfLessonsSolved()).isEqualTo(1);
}
} }