org.powertac.common.TariffEvaluatorTest.java Source code

Java tutorial

Introduction

Here is the source code for org.powertac.common.TariffEvaluatorTest.java

Source

/*
 * Copyright (c) 2013 by John Collins
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.powertac.common;

import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;

import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powertac.common.enumerations.PowerType;
import org.powertac.common.interfaces.Accounting;
import org.powertac.common.interfaces.CustomerModelAccessor;
import org.powertac.common.interfaces.TariffMarket;
import org.powertac.common.repo.TariffRepo;
import org.powertac.common.repo.TariffSubscriptionRepo;
import org.springframework.test.util.ReflectionTestUtils;

/**
 * Test cases for TariffEvaluator.
 * 
 * @author John Collins
 */
public class TariffEvaluatorTest {
    // foundation components
    private Competition competition;
    private TimeService timeService;
    private TariffRepo tariffRepo;
    private TariffSubscriptionRepo tariffSubscriptionRepo;
    private CustomerInfo customer;

    // mocks
    private TariffMarket tariffMarket;
    private Accounting accountingService;

    // brokers and tariffs
    private Broker defaultBroker;
    private Tariff defaultConsumption;
    private Tariff defaultProduction;
    private Broker bob;
    private Broker jim;

    // customer model accessor
    private TestAccessor cma;

    // unit under test
    private TariffEvaluator evaluator;

    @Before
    public void setUp() throws Exception {
        competition = Competition.newInstance("tariff-evaluator-test");
        timeService = new TimeService();
        timeService.setCurrentTime(competition.getSimulationBaseTime().plus(TimeService.HOUR * 7));
        //tariffRepo = new TariffRepo();
        tariffSubscriptionRepo = new TariffSubscriptionRepo();

        // set up mocks
        makeMocks();

        // satisfy dependencies
        ReflectionTestUtils.setField(tariffSubscriptionRepo, "tariffRepo", tariffRepo);
        //ReflectionTestUtils.setField(tariffSubscriptionRepo,
        //                             "tariffMarketService", tariffMarket);

        // set up default tariffs
        defaultBroker = new Broker("default");
        TariffSpecification dcSpec = new TariffSpecification(defaultBroker, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.6));
        defaultConsumption = new Tariff(dcSpec);
        initTariff(defaultConsumption);
        when(tariffMarket.getDefaultTariff(PowerType.CONSUMPTION)).thenReturn(defaultConsumption);

        TariffSpecification dpSpec = new TariffSpecification(defaultBroker, PowerType.PRODUCTION)
                .addRate(new Rate().withValue(0.1));
        defaultProduction = new Tariff(dpSpec);
        initTariff(defaultProduction);
        when(tariffMarket.getDefaultTariff(PowerType.PRODUCTION)).thenReturn(defaultProduction);

        // other brokers
        jim = new Broker("Jim");
        bob = new Broker("Bob");

        // set up customer
        customer = new CustomerInfo("Guinea Pig", 10000).withMultiContracting(true);
        cma = new TestAccessor();

        // uut setup
        evaluator = new TariffEvaluator(cma).withPreferredContractDuration(4).withRationality(0.8);
        ReflectionTestUtils.setField(evaluator, "tariffRepo", tariffRepo);
        ReflectionTestUtils.setField(evaluator, "tariffMarket", tariffMarket);
        ReflectionTestUtils.setField(evaluator, "tariffSubscriptionRepo", tariffSubscriptionRepo);
    }

    private TariffSubscription subscribeTo(Tariff tariff, int count) {
        TariffSubscription subscription = new TariffSubscription(customer, tariff);
        initSubscription(subscription);
        subscription.subscribe(count);
        tariffSubscriptionRepo.add(subscription);
        return subscription;
    }

    private void makeMocks() {
        tariffRepo = mock(TariffRepo.class);
        tariffMarket = mock(TariffMarket.class);
        accountingService = mock(Accounting.class);
    }

    // initializes a tariff. It needs dependencies injected
    private void initTariff(Tariff tariff) {
        ReflectionTestUtils.setField(tariff, "timeService", timeService);
        ReflectionTestUtils.setField(tariff, "tariffRepo", tariffRepo);
        tariff.init();
    }

    // initializes a tariff subscription.
    private void initSubscription(TariffSubscription sub) {
        ReflectionTestUtils.setField(sub, "timeService", timeService);
        ReflectionTestUtils.setField(sub, "tariffMarketService", tariffMarket);
        ReflectionTestUtils.setField(sub, "accountingService", accountingService);
    }

    // ------------------------- tests -----------------------------
    /**
     * Test for no new tariffs case.
     */
    @Test
    public void noTariffTest() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        evaluator.withChunkSize(5000); // just two chunks
        evaluator.evaluateTariffs();
    }

    @Test
    public void singleNewTariffConsumption() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification newTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.59));
        Tariff newTariff = new Tariff(newTS);
        initTariff(newTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(newTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(5000); // just two chunks
        evaluator.evaluateTariffs();
        assertEquals("two tariffs", 2, calls.size());
        assertEquals("-5000 for default", new Integer(-5000), calls.get(defaultConsumption));
        assertEquals("+5000 for new", new Integer(5000), calls.get(newTariff));
    }

    @Test
    public void singleNewTariffSmallChunk() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification newTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.59));
        Tariff newTariff = new Tariff(newTS);
        initTariff(newTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(newTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("two tariffs", 2, calls.size());
        assertEquals("-5000 for default", new Integer(-5000), calls.get(defaultConsumption));
        assertEquals("+5000 for new", new Integer(5000), calls.get(newTariff));
    }

    @Test
    public void twoTariffSplit() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("three tariffs", 3, calls.size());
        assertEquals("-10000 for default", new Integer(-10000), calls.get(defaultConsumption));
        assertEquals("+5000 for bob", new Integer(5000), calls.get(bobTariff));
        assertEquals("+5000 for jim", new Integer(5000), calls.get(jimTariff));
    }

    @Test
    public void withdrawCost() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).withEarlyWithdrawPayment(-500.0)
                .addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("two tariffs", 2, calls.size());
        assertEquals("-10000 for default", new Integer(-10000), calls.get(defaultConsumption));
        assertEquals("+10000 for bob", new Integer(10000), calls.get(bobTariff));
    }

    @Test
    public void BogusWithdrawCost() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).withEarlyWithdrawPayment(1e20)
                .addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(jimTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("two tariffs", 2, calls.size());
        assertEquals("-10000 for default", new Integer(-10000), calls.get(defaultConsumption));
        assertEquals("+10000 for bob", new Integer(10000), calls.get(jimTariff));
    }

    @Test
    public void signupBonus() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION).withSignupPayment(200.0)
                .addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("two tariffs", 2, calls.size());
        assertEquals("-10000 for default", new Integer(-10000), calls.get(defaultConsumption));
        assertEquals("+10000 for jim", new Integer(10000), calls.get(jimTariff));
    }

    @Test
    public void signupCharge() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION).withSignupPayment(-200.0)
                .addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("two tariffs", 2, calls.size());
        assertEquals("-10000 for default", new Integer(-10000), calls.get(defaultConsumption));
        assertEquals("+10000 for bob", new Integer(10000), calls.get(bobTariff));
    }

    @Test
    public void signupCharge2() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION).withSignupPayment(-15.0)
                .addRate(new Rate().withValue(-0.2));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 10.0, 20.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("two tariffs", 2, calls.size());
        assertEquals("-10000 for default", new Integer(-10000), calls.get(defaultConsumption));
        assertEquals("+10000 for bob", new Integer(10000), calls.get(bobTariff));
    }

    @Test
    public void signupCharge3() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION).withSignupPayment(-15.0)
                .addRate(new Rate().withValue(-0.2));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 2.0, 4.0, 2.0, 4.0, 2.0, 4.0, 2.0, 4.0, 2.0, 4.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("two tariffs", 2, calls.size());
        assertEquals("-10000 for default", new Integer(-10000), calls.get(defaultConsumption));
        assertEquals("+10000 for bob", new Integer(10000), calls.get(bobTariff));
    }

    @Test
    public void twoTariffInertia() {
        // do two evals to bump up inertia
        this.noTariffTest();
        evaluator.evaluateTariffs();

        // inertia should now be 0.4
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);
        cma.setInertiaSamples(0.35, 0.45); // half should skip

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("three tariffs", 3, calls.size());
        assertEquals("-5000 for default", new Integer(-5000), calls.get(defaultConsumption));
        assertEquals("+2500 for bob", new Integer(2500), calls.get(bobTariff));
        assertEquals("+2500 for jim", new Integer(2500), calls.get(jimTariff));
    }

    // Test min contract duration. Two tariffs from Jim have equal signup
    // bonus and withdrawal payment, but one has a minDuration half the
    // customer's preferred duration. This one should get all the action.
    @Test
    public void minDurationTest() {
        subscribeTo(defaultConsumption, customer.getPopulation());
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 2).withEarlyWithdrawPayment(-50.0).withSignupPayment(50.0)
                .addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);
        TariffSpecification jimLong = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 4).withEarlyWithdrawPayment(-50.0).withSignupPayment(50.0)
                .addRate(new Rate().withValue(-0.4));
        Tariff jimTariffL = new Tariff(jimLong);
        initTariff(jimTariffL);
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimTariff);
        tariffs.add(jimTariffL);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());

        evaluator.withChunkSize(50); // 200 chunks
        evaluator.evaluateTariffs();
        assertEquals("two changes", 2, calls.size());
        assertEquals("-10000 for default", new Integer(-10000), calls.get(defaultConsumption));
        assertEquals("+10000 for jim-long", new Integer(10000), calls.get(jimTariff));
    }

    // Revoke to default tariff
    @Test
    public void revokeToDefault() {
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.4, 0.6);

        // distribute all customers across jim & bob
        subscribeTo(bobTariff, 5000);
        subscribeTo(jimTariff, 5000);

        // revoke Jim's tariff - should move everyone to Bob
        jimTariff.setState(Tariff.State.KILLED);
        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        evaluator.evaluateTariffs();
        assertEquals("two calls", 2, calls.size());
        assertEquals("-5000 for jim", new Integer(-5000), calls.get(jimTariff));
        assertEquals("none for default", null, calls.get(defaultConsumption));
        assertEquals("+5000 for bob", new Integer(5000), calls.get(bobTariff));
    }

    // Revoke to superseding tariff
    @Test
    public void revokeSuperseding() {
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.45, 0.55);

        // distribute all customers across jim & bob
        subscribeTo(bobTariff, 5000);
        subscribeTo(jimTariff, 5000);

        // revoke Jim's tariff, supersede it
        jimTariff.setState(Tariff.State.KILLED);
        TariffSpecification jimSTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).addSupersedes(jimTariff.getId())
                .addRate(new Rate().withValue(-0.4));
        Tariff jimSuper = new Tariff(jimSTS);
        when(tariffRepo.findTariffById(jimTariff.getId())).thenReturn(jimTariff);
        initTariff(jimSuper);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        tariffs.add(jimSuper);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        evaluator.evaluateTariffs();
        assertEquals("three calls", 3, calls.size());
        assertEquals("-5000 for jim", new Integer(-5000), calls.get(jimTariff));
        assertEquals("+2500 for jimSuper", new Integer(2500), calls.get(jimSuper));
        assertEquals("+2500 for bob", new Integer(2500), calls.get(bobTariff));
    }

    // Revoke to superseding tariff
    @Test
    public void revokeToKilledSuperseding() {
        TariffSpecification bobTS = new TariffSpecification(bob, PowerType.CONSUMPTION)
                .addRate(new Rate().withValue(-0.4));
        Tariff bobTariff = new Tariff(bobTS);
        initTariff(bobTariff);
        TariffSpecification jimTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).addRate(new Rate().withValue(-0.4));
        Tariff jimTariff = new Tariff(jimTS);
        initTariff(jimTariff);

        double[] profile = { 1.0, 2.0 };
        cma.capacityProfile = profile;
        cma.setChoiceSamples(0.45, 0.55);

        // distribute all customers across jim & bob
        subscribeTo(bobTariff, 5000);
        subscribeTo(jimTariff, 5000);

        // revoke Jim's tariff, supersede it
        jimTariff.setState(Tariff.State.KILLED);
        TariffSpecification jimSTS = new TariffSpecification(jim, PowerType.CONSUMPTION)
                .withMinDuration(TimeService.DAY * 5).addSupersedes(jimTariff.getId())
                .addRate(new Rate().withValue(-0.4));
        Tariff jimSuper = new Tariff(jimSTS);
        when(tariffRepo.findTariffById(jimTariff.getId())).thenReturn(jimTariff);
        initTariff(jimSuper);
        // Revoke the superseding tariff
        jimSuper.setState(Tariff.State.KILLED);

        // capture calls to tariffMarket
        final HashMap<Tariff, Integer> calls = new HashMap<Tariff, Integer>();
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) {
                Object[] args = invocation.getArguments();
                assertEquals("correct customer", customer, args[1]);
                calls.put((Tariff) args[0], (Integer) args[2]);
                return null;
            }
        }).when(tariffMarket).subscribeToTariff(any(Tariff.class), any(CustomerInfo.class), anyInt());
        ArrayList<Tariff> tariffs = new ArrayList<Tariff>();
        tariffs.add(defaultConsumption);
        tariffs.add(bobTariff);
        //tariffs.add(jimSuper);
        when(tariffRepo.findRecentActiveTariffs(anyInt(), any(PowerType.class))).thenReturn(tariffs);

        evaluator.evaluateTariffs();
        assertEquals("two calls", 2, calls.size());
        assertEquals("-5000 for jim", new Integer(-5000), calls.get(jimTariff));
        assertEquals("+5000 for defaultConsumption", new Integer(5000), calls.get(bobTariff));
    }

    // Revoke to better tariff

    // Revoke to superseding tariff, with inertia

    // --------------- model accessor ------------------------------------
    class TestAccessor implements CustomerModelAccessor {
        // values to return
        double[] capacityProfile;
        double brokerSwitchFactor = 0.05;

        double[] choiceSamples = { 0.5 };
        int choicePtr = 0;
        double[] inertiaSamples = { 0.5 };
        int inertiaPtr = 0;

        TestAccessor() {
            super();
        }

        @Override
        public CustomerInfo getCustomerInfo() {
            return customer;
        }

        @Override
        public double[] getCapacityProfile(Tariff tariff) {
            return capacityProfile;
        }

        @Override
        public double getBrokerSwitchFactor(boolean isSuperseding) {
            double multiplier = isSuperseding ? 0.0 : 1.0;
            return multiplier * brokerSwitchFactor;
        }

        @Override
        public double getTariffChoiceSample() {
            if (choicePtr >= choiceSamples.length)
                choicePtr = 0;
            return choiceSamples[choicePtr++];
        }

        @Override
        public double getInertiaSample() {
            if (inertiaPtr >= inertiaSamples.length)
                inertiaPtr = 0;
            return inertiaSamples[inertiaPtr++];
        }

        // sets the choice sequence
        void setChoiceSamples(double... samples) {
            choiceSamples = samples;
        }

        // sets the inertia sequence
        void setInertiaSamples(double... samples) {
            inertiaSamples = samples;
        }
    }
}