text:
// Copyright 2012 Selenium committers
// Copyright 2012 Software Freedom Conservancy
//
// 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.

/**
 * @fileoverview Entry point for the SafariDriver extension's global script.
 */

goog.provide('safaridriver.extension');

goog.require('goog.asserts');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger');
goog.require('safaridriver.console');
goog.require('safaridriver.extension.Server');
goog.require('safaridriver.extension.Session');
goog.require('safaridriver.extension.TabManager');
goog.require('safaridriver.message');
goog.require('safaridriver.message.Alert');
goog.require('safaridriver.message.Connect');
goog.require('webdriver.Session');
goog.require('webdriver.WebDriver');

/**
 * Initializes the SafariDriver extension's global page.
 */
safaridriver.extension.init = function() {
  goog.debug.LogManager.getRoot().setLevel(goog.debug.Logger.Level.ALL);
  safaridriver.console.init();

  safaridriver.extension.LOG_.info('Creating global session...');
  safaridriver.extension.tabManager_ = new safaridriver.extension.TabManager();
  safaridriver.extension.session_ = new safaridriver.extension.Session(
      safaridriver.extension.tabManager_);

  safaridriver.extension.LOG_.info('Creating debug driver...');
  var server = safaridriver.extension.createSessionServer_();
  safaridriver.extension.driver = new webdriver.WebDriver(
      new webdriver.Session('debug', {}), server);
  goog.exportSymbol('driver', safaridriver.extension.driver);

  // Now that we're initialized, we sit and wait for a page to send us a client
  // to attempt connecting to.
  safaridriver.extension.LOG_.info('Waiting for connect command...');
  safari.application.addEventListener('message',
      safaridriver.extension.onMessage_, false);
};

/**
 * @type {!goog.debug.Logger}
 * @const
 * @private
 */
safaridriver.extension.LOG_ = goog.debug.Logger.getLogger(
    'safaridriver.extension');

/**
 * Global tab manager shared by each instance; lazily initialized in
 * {@link safaridriver.extension.init}.
 * @type {!safaridriver.extension.TabManager}
 * @private
 */
safaridriver.extension.tabManager_;

/**
 * Global session shared by eash clinet; lazily initialized in
 * {@link safaridriver.extension.init}.
 * @type {!safaridriver.extension.Session}
 * @private
 */
safaridriver.extension.session_;

/**
 * An instance of {@link webdriver.WebDriver}, provided for interacting with
 * the extension via the global page's REPL.
 * @type {webdriver.WebDriver}
 */
safaridriver.extension.driver;

/**
 * The number of WebDriver client connections active with this extension.
 * @type {number}
 * @private
 */
safaridriver.extension.numConnections_ = 0;

/**
 * Responds to a message from an injected script.
 * @param {!SafariExtensionMessageEvent} e The event.
 * @private
 */
safaridriver.extension.onMessage_ = function(e) {
  var message = safaridriver.message.fromEvent(e);
  switch (message.getType()) {
    case safaridriver.message.Connect.TYPE:
      var url = (/** @type {!safaridriver.message.Connect} */ message).getUrl();
      var server = safaridriver.extension.createSessionServer_();
      server.connect(url).
          then(function() {
            safaridriver.extension.LOG_.info('Connected to client: ' + url);
            safaridriver.extension.numConnections_++;
            server.onDispose(function() {
              safaridriver.extension.numConnections_--;
            });
          }, function(e) {
            safaridriver.extension.LOG_.severe(
                'Failed to connect to client: ' + url, e);
          });
      break;

    case safaridriver.message.Alert.TYPE:
      goog.asserts.assert(e.name === 'canLoad',
          'Received an async alert message');
      if (!safaridriver.extension.session_.isExecutingCommand()) {
        safaridriver.extension.session_.setUnhandledAlertText(
            message.getMessage());
      }

      // TODO: Fully support alerts. See
      // http://code.google.com/p/selenium/issues/detail?id=3862
      e.message = !!safaridriver.extension.numConnections_;
      e.stopPropagation();
      break;
  }
};

/**
 * Creates a new session. The SafariDriver supports multiple sessions, each
 * with their own timeouts, but they all share the same tab manager.
 * @return {!safaridriver.extension.Server} The new server.
 * @private
 */
safaridriver.extension.createSessionServer_ = function() {
  return new safaridriver.extension.Server(safaridriver.extension.session_);
};


text:
package com.newrelic.metrics.publish.binding;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;

import org.junit.Test;

public class ComponentDataTest {

    @Test
    public void testSerialize() {

        Context context = BindingFactory.createContext();
        Request request = context.createRequest();

        // component to test
        ComponentData component = new ComponentData();
        component.guid = "com.test.guid";
        component.name = "test component";

        // test metrics
        request.addMetric(component, "test metric", 7.0f);
        request.addMetric(component, "second test metric", 33.2f);

        // serialize test
        HashMap<String, Object> data = component.serialize(request);

        // expected outcome
        HashMap<String, Object> expected = new HashMap<String, Object>();
        expected.put("guid", "com.test.guid");
        expected.put("name", "test component");
        expected.put("duration", 60);

        HashMap<String, Object> expectedMetrics = new HashMap<String, Object>();
        expectedMetrics.put("test metric", Arrays.<Number>asList(7.0f, 1, 7.0f, 7.0f, 49.0f));
        expectedMetrics.put("second test metric", Arrays.<Number>asList(33.2f, 1, 33.2f, 33.2f, 1102.24f));
        expected.put("metrics", expectedMetrics);

        assertEquals(expected, data);
    }

    @Test
    public void testSerializeComponentWithNoMetrics() {

        Context context = BindingFactory.createContext();
        Request request = context.createRequest();

        // component to test
        ComponentData component = new ComponentData();
        component.guid = "com.test.guid";
        component.name = "test component";

        // serialize test
        HashMap<String, Object> data = component.serialize(request);

        // expected outcome
        HashMap<String, Object> expected = new HashMap<String, Object>();

        assertEquals(expected, data);
    }

    @Test
    public void testDefaultDuration() {

        Context context = BindingFactory.createContext();
        Request request = context.createRequest();

        // component to test
        ComponentData component = new ComponentData();
        component.guid = "com.test.guid";
        component.name = "test component";

        // test metrics
        request.addMetric(component, "test metric", 10);

        // serialize test
        HashMap<String, Object> data = component.serialize(request);

        // expected outcome
        HashMap<String, Object> expected = new HashMap<String, Object>();
        expected.put("guid", "com.test.guid");
        expected.put("name", "test component");
        expected.put("duration", 60);

        HashMap<String, Object> expectedMetrics = new HashMap<String, Object>();
        expectedMetrics.put("test metric", Arrays.<Number>asList(10.0f, 1, 10.0f, 10.0f, 100.0f));
        expected.put("metrics", expectedMetrics);

        assertEquals(expected, data);
    }

    @Test
    public void testCalculatedDuration() {
        Context context = BindingFactory.createContext();
        Request request = BindingFactory.createRequest(context);

        // component to test
        ComponentData component = new ComponentData();
        component.guid = "com.test.guid";
        component.name = "test component";

        // set last successful timestamp to 30 seconds ago
        Date now = new Date();
        component.setLastSuccessfulReportedAt(new Date(now.getTime() - 30000));

        // test metrics
        request.addMetric(component, "test metric", 10);

        // serialize test
        HashMap<String, Object> data = component.serialize(request);

        // expected outcome
        HashMap<String, Object> expected = new HashMap<String, Object>();
        expected.put("guid", "com.test.guid");
        expected.put("name", "test component");
        expected.put("duration", 30);

        HashMap<String, Object> expectedMetrics = new HashMap<String, Object>();
        expectedMetrics.put("test metric", Arrays.<Number>asList(10.0f, 1, 10.0f, 10.0f, 100.0f));
        expected.put("metrics", expectedMetrics);

        assertEquals(expected, data);
    }

    @Test
    public void testRoundedCalculatedDuration() {
        Context context = BindingFactory.createContext();
        Request request = BindingFactory.createRequest(context);

        // component to test
        ComponentData component = new ComponentData();
        component.guid = "com.test.guid";
        component.name = "test component";

        // set last successful timestamp to 30.4 seconds ago
        Date now = new Date();
        component.setLastSuccessfulReportedAt(new Date(now.getTime() - 30400));

        // test metrics
        request.addMetric(component, "test metric", 10);

        // serialize test
        HashMap<String, Object> data = component.serialize(request);

        // expected outcome
        HashMap<String, Object> expected = new HashMap<String, Object>();
        expected.put("guid", "com.test.guid");
        expected.put("name", "test component");
        expected.put("duration", 31);

        HashMap<String, Object> expectedMetrics = new HashMap<String, Object>();
        expectedMetrics.put("test metric", Arrays.<Number>asList(10.0f, 1, 10.0f, 10.0f, 100.0f));
        expected.put("metrics", expectedMetrics);

        assertEquals(expected, data);
    }
}

redpajama_set_name: RedPajamaGithub


text:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Admin_Panel_Transactions extends CI_Controller {

    public function index(){
        if($this->session->userdata("admin_id")){
            $x = 0;
            $data['number'] = 1;
            $data['transactions'] = $this->db->get('transaction')->result();

            if($_POST){
                foreach($this->input->post('delete') as $delete){
                    $this->delete($this->input->post('delete')[$x]);
                    $x++;
                }
                redirect('Admin_Panel_Transactions');
            }
            $this->load->view('admin_panel/header');
            $this->load->view('admin_panel/transactions_index.php', $data);
            $this->load->view('admin_panel/footer');
        } else {
            redirect('Admin_Panel_Access/login');
        }
    }

    public function delete($id){
        $data['transaction_status'] = 6;
        $this->db->update('transaction',$data, array('id' => $id));
    }

    public function edit($id){

        if($this->session->userdata("admin_id")){
            $data['transaction'] = $this->db->get_where('transaction', array('id' => $id))->result();

            if($_POST){
                $input['quantity'] = $this->input->post('quantity');
                $input['price'] = $this->input->post('price');
                $input['transaction_status'] = $this->input->post('status');

                $this->db->update('transaction', $input, array('id'=>$id));
                redirect('Admin_Panel_Transactions');
            }
            $this->load->view('admin_panel/header');
            $this->load->view('admin_panel/transaction_edit.php', $data);
            $this->load->view('admin_panel/footer');
        } else {
            redirect('Admin_Panel_Access/login');
        }
    }

}


text:
/* @flow */

import Type from './Type';
import type Validation, {ErrorTuple, IdentifierPath} from '../Validation';
import getErrorMessage from "../getErrorMessage";

/**
 * # ThisType
 * Captures a reference to a particular instance of a class or a value,
 * and uses that value to perform an identity check.
 * In the case that `this` is undefined, any value will be permitted.
 */
export default class ThisType<T> extends Type {
  typeName: string = 'ThisType';

  recorded: ? T;

  *errors (validation: Validation<any>, path: IdentifierPath, input: any): Generator<ErrorTuple, void, void> {
    const {recorded} = this;
    if (input === recorded) {
      return;
    }
    else if (typeof recorded === 'function' && input instanceof recorded) {
      return;
    }
    else if (recorded != null) {
      yield [path, getErrorMessage('ERR_EXPECT_THIS'), this];
    }
  }

  accepts (input: any): boolean {
    const {recorded} = this;
    if (input === recorded) {
      return true;
    }
    else if (typeof recorded === 'function' && input instanceof recorded) {
      return true;
    }
    else if (recorded != null) {
      return false;
    }
    else {
      return true;
    }
  }

  compareWith (input: Type<any>): -1 | 0 | 1 {
    if (!(input instanceof ThisType)) {
      return -1;
    }
    else if (input.recorded && this.recorded) {
      return input.recorded === this.recorded ? 0 : -1;
    }
    else if (this.recorded) {
      return 0;
    }
    else {
      return 1;
    }
  }

  /**
   * Get the inner type.
   */
  unwrap (): Type<T> {
    return this;
  }

  toString (withBinding?: boolean): string {
    return 'this';
  }

  toJSON () {
    return {
      typeName: this.typeName,
    };
  }
}


text:
Slides can be seen via the official [go-talks](https://go-talks.appspot.com) app. Just use the path to the .slide files from this repo in the url

Example: [https://go-talks.appspot.com/github.com/lggomez/go-talks/2018/the-go-routine.slide#1](https://go-talks.appspot.com/github.com/lggomez/go-talks/2018/the-go-routine.slide#1)

## Index

### 2017
* [Go meetup Buenos Aires 2017 (español)](https://go-talks.appspot.com/github.com/lggomez/go-talks/2017/go-dep.slide#1)
### 2018
* [Go meetup Buenos Aires 2018 (español)](https://go-talks.appspot.com/github.com/lggomez/go-talks/2018/the-go-routine.slide#1)
* [Small presentation on advanced data types (español)](https://go-talks.appspot.com/github.com/lggomez/go-talks/2018_misc/adv_data_types/8.slide#1)
### 2019
* [Go meetup Buenos Aires 2019 II - 2019/12/18 (español)](https://go-talks.appspot.com/github.com/lggomez/go-talks/2019/12-18-MELI/gophers-attack.slide#1) / [examples](https://github.com/lggomez/go-talks/tree/master/2019/12-18-MELI/example)
### 2021
* [Go meetup Buenos Aires 12th edition, 2021 - 2021/11/23 (español)](https://github.com/lggomez/go-talks/blob/master/2021/2021-11-23-PEYA/Meetup%20Go%2012.pdf)


text:
Jakkat
======

Jak's cat tool.

This is essentially a cross platform tool inspired by cpp (the C preprocessor)
which is used to assemble a text document from smaller templates (e.g.
for assembling songs for [this guy](https://github.com/alzwded/JakMuse))

Syntax
------

### Plain definitions

```
[x=value]
```

### Arithmetics

```
[x:=2+3]        # the 'x' symbol will be '5'
[y:=2*3]
[z:=x+y]        # 'z' will be '11'
[z=x+y]         # 'z' will be 'x+y'
```

### Substitutions

```
[x=y]
<x> is 10.      # '<x>' will be replaced by 'y'
```

### Inclusions

```
{file.inc}      # the file 'file.inc' will have its contents inserted
{10xfile.inc}   # the file will be included 10 times
{file.inc$x=V$y:=2+3} # pass symbol definitions to the reentrant parser
                      # the symbol definitions passed this way will not
                      # be visible to the rest of the file; they apply
                      # only to the parser reading 'file.inc'
{3xfile.inc$x=V} # etc
```

### Command line arguments

```sh
jakkat [-w output_file] [-DConstant=Definition...] inputfile1 inputfile2...
```

The constant definitions passed through `-D` are redefined on each
entry (i.e. for each input file).


text:
package dlock

import (
	"context"

	etcd "github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/clientv3/concurrency"
)

// DLock is a handle to a distributed lock.
type DLock interface {
	// Lock acquries the distributed lock, blocking if necessary.  If
	// the lock is acquired, it returns a context that should be used
	// in any subsequent blocking requests, so that if you lose the lock,
	// the requests get cancelled correctly.
	Lock(context.Context) (context.Context, error)
	// Unlock releases the distributed lock.
	Unlock(context.Context) error
}

type etcdImpl struct {
	client *etcd.Client
	prefix string

	session *concurrency.Session
	mutex   *concurrency.Mutex
}

// NewDLock attempts to acquire a distributed lock that locks a given prefix
// in the data store.
func NewDLock(client *etcd.Client, prefix string) DLock {
	return &etcdImpl{
		client: client,
		prefix: prefix,
	}
}

func (d *etcdImpl) Lock(ctx context.Context) (context.Context, error) {
	// The default TTL is 60 secs which means that if a node dies, it
	// still holds the lock for 60 secs, which is too high.
	session, err := concurrency.NewSession(d.client, concurrency.WithContext(ctx), concurrency.WithTTL(15))
	if err != nil {
		return nil, err
	}

	mutex := concurrency.NewMutex(session, d.prefix)
	if err := mutex.Lock(ctx); err != nil {
		return nil, err
	}

	ctx, cancel := context.WithCancel(ctx)
	go func() {
		select {
		case <-ctx.Done():
		case <-session.Done():
			cancel()
		}
	}()

	d.session = session
	d.mutex = mutex
	return ctx, nil
}

func (d *etcdImpl) Unlock(ctx context.Context) error {
	if err := d.mutex.Unlock(ctx); err != nil {
		return err
	}
	return d.session.Close()
}


text:
namespace mojo {
namespace examples {

class DBusEchoApp : public ApplicationDelegate {
 public:
  DBusEchoApp() {}
  virtual ~DBusEchoApp() {}

  virtual void Initialize(ApplicationImpl* app) MOJO_OVERRIDE {
    app->ConnectToService(
        "dbus:org.chromium.EchoService/org/chromium/MojoImpl", &echo_service_);

    echo_service_->EchoString(
        String::From("who"),
        base::Bind(&DBusEchoApp::OnEcho, base::Unretained(this)));
  }

 private:
  void OnEcho(String echoed) {
    LOG(INFO) << "echo'd " << echoed;
  }

  EchoServicePtr echo_service_;

  DISALLOW_COPY_AND_ASSIGN(DBusEchoApp);
};

}  // namespace examples
}  // namespace mojo

MojoResult MojoMain(MojoHandle shell_handle) {
  mojo::ApplicationRunner runner(new mojo::examples::DBusEchoApp);
  return runner.Run(shell_handle);
}


text:
using System;

namespace HelloWorld.Script.CsScript.Action
{

    /// <summary>
    /// ActionID定义描述
    /// </summary>
    public class ActionIDDefine
    {
        ///<summary>
        ///hello
        ///</summary>
        public const Int16 Cst_Action1001 = 1001;

    }
}


text:
<?php

declare(strict_types=1);

namespace Doctrine\ODM\MongoDB\Tests\Functional\Ticket;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Tests\BaseTest;
use Traversable;
use function is_array;

class MODM92Test extends BaseTest
{
    public function testDocumentWithEmbeddedDocuments()
    {
        $embeddedDocuments = [new MODM92TestEmbeddedDocument('foo')];

        $testDoc = new MODM92TestDocument();
        $testDoc->setEmbeddedDocuments($embeddedDocuments);
        $this->dm->persist($testDoc);
        $this->dm->flush();
        $this->dm->clear();

        $testDoc = $this->dm->find(MODM92TestDocument::class, $testDoc->id);
        $this->assertEquals($embeddedDocuments, $testDoc->embeddedDocuments->toArray());

        $embeddedDocuments = [new MODM92TestEmbeddedDocument('bar')];

        $testDoc->setEmbeddedDocuments($embeddedDocuments);
        $this->assertEquals($embeddedDocuments, $testDoc->embeddedDocuments->toArray());

        $this->dm->flush();
        $this->dm->clear();
        $testDoc = $this->dm->find(MODM92TestDocument::class, $testDoc->id);

        $this->assertEquals($embeddedDocuments, $testDoc->embeddedDocuments->toArray());
    }
}

/** @ODM\Document */
class MODM92TestDocument
{
    /** @ODM\Id */
    public $id;

    // Note: Test case fails with default "pushAll" strategy, but "set" works
    /** @ODM\EmbedMany(targetDocument=MODM92TestEmbeddedDocument::class) */
    public $embeddedDocuments;

    public function __construct()
    {
        $this->embeddedDocuments = new ArrayCollection();
    }

    /**
     * Sets children
     *
     * If $images is not an array or Traversable object, this method will simply
     * clear the images collection property.  If any elements in the parameter
     * are not an Image object, this method will attempt to convert them to one
     * by mapping array indexes (size URL's are required, cropMetadata is not).
     * Any invalid elements will be ignored.
     *
     * @param array|Traversable $children
     */
    public function setEmbeddedDocuments($embeddedDocuments)
    {
        $this->embeddedDocuments->clear();

        if (! (is_array($embeddedDocuments) || $embeddedDocuments instanceof Traversable)) {
            return;
        }

        foreach ($embeddedDocuments as $embeddedDocument) {
            $this->embeddedDocuments->add($embeddedDocument);
        }
    }
}

/** @ODM\EmbeddedDocument */
class MODM92TestEmbeddedDocument
{
    /** @ODM\Field(type="string") */
    public $name;

    public function __construct($name)
    {
        $this->name = $name;
    }
}


text:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
	<SOAP-ENV:Body>
		<GetPhotoStatusResponse xmlns="http://localhost/api/soap/eyefilm">
			<fileid>{{ fileid }}</fileid>
            <offset>{{ offset }}</offset>
		</GetPhotoStatusResponse>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>


text:
[![Build Status](https://travis-ci.org/golmansax/eslint-config-golmansax.svg?branch=master)](https://travis-ci.org/golmansax/eslint-config-golmansax)

Need to install the following packages along with this one:
* `babel-eslint`
* `eslint >= 1.4`
* `eslint-config-airbnb >= 0.1.0`
* `eslint-plugin-react`

## Usage

### ES6 + React
`"extends": "golmansax"`

### React Native
`"extends": "golmansax/react-native"`


text:
require 'rails_helper'

describe User do
  describe "validations" do
    describe "invitation_code" do
      context "when configured to use invitation codes" do
        before do
          stub(User).using_invitation_code? {true}
        end

        it "only accepts valid invitation codes" do
          User::INVITATION_CODES.each do |v|
            should allow_value(v).for(:invitation_code)
          end
        end

        it "can reject invalid invitation codes" do
          %w['foo', 'bar'].each do |v|
            should_not allow_value(v).for(:invitation_code)
          end
        end
      end

      context "when configured not to use invitation codes" do
        before do
          stub(User).using_invitation_code? {false}
        end

        it "skips this validation" do
          %w['foo', 'bar', nil, ''].each do |v|
            should allow_value(v).for(:invitation_code)
          end
        end
      end
    end
  end
end


text:
package org.dashbuilder.renderer.lienzo.client;

import javax.enterprise.context.Dependent;
import javax.inject.Inject;

@Dependent
public class LienzoLineChartDisplayer extends LienzoXYChartDisplayer<LienzoLineChartDisplayer.View> {

    public interface View extends LienzoXYChartDisplayer.View<LienzoLineChartDisplayer> {

    }

    private View view;

    public LienzoLineChartDisplayer() {
        this(new LienzoLineChartDisplayerView());
    }

    @Inject
    public LienzoLineChartDisplayer(View view) {
        this.view = view;
        this.view.init(this);
    }

    @Override
    public View getView() {
        return view;
    }
}


text:
package main

import (
	"sort"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestTaskString(t *testing.T) {
	t.Parallel()

	// selected
	assert.Equal(
		t,
		(&Task{ID: "foo", Desc: "test", Selected: true}).String(),
		"foo: test (*)",
	)

	// unselected
	assert.Equal(
		t,
		(&Task{ID: "foo", Desc: "test", Selected: false}).String(),
		"foo: test",
	)

	// done
	assert.Equal(
		t,
		(&Task{ID: "foo", Desc: "test", Done: true}).String(),
		"foo: test (done)",
	)
}

func TestByActive(t *testing.T) {
	t.Parallel()

	now := time.Now()
	past := &Task{ID: "past", Active: now.Add(-1 * time.Second)}
	present := &Task{ID: "present", Active: now}
	future := &Task{ID: "future", Active: now.Add(time.Second)}

	tasks := []*Task{present, future, past}

	sort.Sort(ByActive(tasks))

	assert.Equal(
		t,
		tasks,
		[]*Task{past, present, future},
	)
}

func TestTaskMarkDone(t *testing.T) {
	t.Parallel()

	// task with no Repeat
	task := Task{Selected: true}
	assert.False(t, task.Done)
	assert.True(t, task.Selected)

	task.MarkDone()
	assert.True(t, task.Done)
	assert.False(t, task.Selected)

	// task with a Repeat
	now := time.Now()
	task = Task{Selected: true, Repeat: time.Second}
	task.MarkDone()

	assert.False(t, task.Done)
	assert.True(t, task.Active.After(now))
}


text:
#pragma once

template<typename T, typename Other>
struct is_same{
    static const bool value = false;
};

template<typename T>
struct is_same<T, T> {
    static const bool value = true;
};

template<bool, typename T, typename O>
struct cond_type;

template<typename T, typename Other>
struct cond_type<true, T, Other> {
    typedef T type;
};

template<typename T, typename Other>
struct cond_type<false, T, Other> {
    typedef Other type;
};

template<typename T>
struct baseOutType {
    typedef typename cond_type< is_same<T, cdouble>::value ||
                                is_same<T, double>::value,
                                double,
                                float>::type type;
};

template<typename T>
inline T mean(const Array<T>& in)
{
    T out = reduce_all<af_add_t, T, T>(in);
    T result = division(out, in.elements());
    return result;
}

template<typename T, typename wType>
inline T mean(const Array<T>& in, const Array<wType>& weights)
{
    Array<T> wts   = cast<T>(weights);

    dim4 iDims = in.dims();

    Array<T> wtdInput = arithOp<T, af_mul_t>(in, wts, iDims);

    T wtdSum = reduce_all<af_add_t, T, T>(wtdInput);
    wType wtsSum = reduce_all<af_add_t, wType, wType>(weights);

    return division(wtdSum, wtsSum);
}

template<typename T>
inline Array<T> mean(const Array<T>& in, dim_type dim)
{
    Array<T> redArr = reduce<af_add_t, T, T>(in, dim);

    dim4 iDims = in.dims();
    dim4 oDims = redArr.dims();

    Array<T> cnstArr = createValueArray<T>(oDims, scalar<T>(iDims[dim]));
    Array<T> result  = arithOp<T, af_div_t>(redArr, cnstArr, oDims);

    return result;
}

template<typename T>
inline Array<T> mean(const Array<T>& in, const Array<T>& wts, dim_type dim)
{
    dim4 iDims = in.dims();

    Array<T> wtdInput = arithOp<T, af_mul_t>(in, wts, iDims);
    Array<T> redArr   = reduce<af_add_t, T, T>(wtdInput, dim);
    Array<T> wtsSum   = reduce<af_add_t, T, T>(wts, dim);

    dim4 oDims = redArr.dims();

    Array<T> result = arithOp<T, af_div_t>(redArr, wtsSum, oDims);

    return result;
}


text:
from setuptools import setup

setup(name='kicadsearch',
      version='0.3.1',
      author='Arvid Juskaitis',
      author_email='arvydas.juskaitis@gmail.com',
      url='https://github.com/arvjus/kicad-search',
      packages=['kicadsearch', ],
      license='LICENSE.txt',
      description='Freetext searching in KiCad EDA component libraries',
      keywords=['kicad', 'search'],
      scripts=['bin/kicadsearch', 'bin/kicadsearch-index'],
      install_requires=[
          'Whoosh >=2.7.0'
      ], )


text:
require 'trollop'
require 'yaml'
require 'aws/s3'

dir = "#{File.expand_path(File.dirname(__FILE__))}"
datadir = "#{dir}/data"

params = Trollop::options do
  opt :date, "Specify date of recording to upload (e.g. 20120521)", :type => :string
end

@s3_config = YAML.load_file("#{dir}/config/s3.yml")
AWS::S3::Base.establish_connection!(:access_key_id => @s3_config['access_key_id'], :secret_access_key => @s3_config['secret_access_key'])

params[:date] = Dir.glob("#{datadir}/20*").reject{|f| f.index('.tar.gz')}.map{|f| f.split('/').last}.sort.last

puts "Gzipping and uploading replay from #{params[:date]}"
system "cd #{datadir} && tar -czf #{params[:date]}.tar.gz #{params[:date]}"
AWS::S3::Base.establish_connection!(:access_key_id => @s3_config['access_key_id'], :secret_access_key => @s3_config['secret_access_key'])
file = "#{datadir}/#{params[:date]}.tar.gz"
s3_file = "#{@s3_config['directory']}/#{params[:date]}.tar.gz"
AWS::S3::S3Object.store(s3_file, open(file), @s3_config['bucket'], :access => :private)


text:
package docker

import (
	"io/ioutil"
	"os"

	. "github.com/clearcontainers/tests"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("export", func() {
	var (
		args []string
		id   string
	)

	BeforeEach(func() {
		id = randomDockerName()
		args = []string{"run", "-td", "--name", id, Image}
		runDockerCommand(0, args...)
	})

	AfterEach(func() {
		Expect(RemoveDockerContainer(id)).To(BeTrue())
		Expect(ExistDockerContainer(id)).NotTo(BeTrue())
	})

	Describe("export with docker", func() {
		Context("export a container", func() {
			It("should export filesystem as a tar archive", func() {
				file, err := ioutil.TempFile(os.TempDir(), "latest.tar")
				Expect(err).ToNot(HaveOccurred())
				defer os.Remove(file.Name())
				_, _, exitCode := DockerExport("--output", file.Name(), id)
				Expect(exitCode).To(Equal(0))
				Expect(file.Name()).To(BeAnExistingFile())
				fileInfo, err := file.Stat()
				Expect(err).ToNot(HaveOccurred())
				Expect(fileInfo.Size).NotTo(Equal(0))
				err = file.Close()
				Expect(err).ToNot(HaveOccurred())
			})
		})
	})
})


text:
package instructions

import (
	"errors"
	"strings"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/strslice"
	specs "github.com/opencontainers/image-spec/specs-go/v1"
)

// KeyValuePair represent an arbitrary named value (useful in slice instead of map[string] string to preserve ordering)
type KeyValuePair struct {
	Key   string
	Value string
}

func (kvp *KeyValuePair) String() string {
	return kvp.Key + "=" + kvp.Value
}

// Command is implemented by every command present in a dockerfile
type Command interface {
	Name() string
}

// KeyValuePairs is a slice of KeyValuePair
type KeyValuePairs []KeyValuePair

// withNameAndCode is the base of every command in a Dockerfile (String() returns its source code)
type withNameAndCode struct {
	code string
	name string
}

func (c *withNameAndCode) String() string {
	return c.code
}

// Name of the command
func (c *withNameAndCode) Name() string {
	return c.name
}

func newWithNameAndCode(req parseRequest) withNameAndCode {
	return withNameAndCode{code: strings.TrimSpace(req.original), name: req.command}
}

// SingleWordExpander is a provider for variable expansion where 1 word => 1 output
type SingleWordExpander func(word string) (string, error)

// SupportsSingleWordExpansion interface marks a command as supporting variable expansion
type SupportsSingleWordExpansion interface {
	Expand(expander SingleWordExpander) error
}

// PlatformSpecific adds platform checks to a command
type PlatformSpecific interface {
	CheckPlatform(platform string) error
}

func expandKvp(kvp KeyValuePair, expander SingleWordExpander) (KeyValuePair, error) {
	key, err := expander(kvp.Key)
	if err != nil {
		return KeyValuePair{}, err
	}
	value, err := expander(kvp.Value)
	if err != nil {
		return KeyValuePair{}, err
	}
	return KeyValuePair{Key: key, Value: value}, nil
}
func expandKvpsInPlace(kvps KeyValuePairs, expander SingleWordExpander) error {
	for i, kvp := range kvps {
		newKvp, err := expandKvp(kvp, expander)
		if err != nil {
			return err
		}
		kvps[i] = newKvp
	}
	return nil
}

func expandSliceInPlace(values []string, expander SingleWordExpander) error {
	for i, v := range values {
		newValue, err := expander(v)
		if err != nil {
			return err
		}
		values[i] = newValue
	}
	return nil
}

// EnvCommand : ENV key1 value1 [keyN valueN...]
type EnvCommand struct {
	withNameAndCode
	Env KeyValuePairs // kvp slice instead of map to preserve ordering
}

// Expand variables
func (c *EnvCommand) Expand(expander SingleWordExpander) error {
	return expandKvpsInPlace(c.Env, expander)
}

// MaintainerCommand : MAINTAINER maintainer_name
type MaintainerCommand struct {
	withNameAndCode
	Maintainer string
}

// NewLabelCommand creates a new 'LABEL' command
func NewLabelCommand(k string, v string, NoExp bool) *LabelCommand {
	kvp := KeyValuePair{Key: k, Value: v}
	c := "LABEL "
	c += kvp.String()
	nc := withNameAndCode{code: c, name: "label"}
	cmd := &LabelCommand{
		withNameAndCode: nc,
		Labels: KeyValuePairs{
			kvp,
		},
		noExpand: NoExp,
	}
	return cmd
}

// LabelCommand : LABEL some json data describing the image
//
// Sets the Label variable foo to bar,
//
type LabelCommand struct {
	withNameAndCode
	Labels   KeyValuePairs // kvp slice instead of map to preserve ordering
	noExpand bool
}

// Expand variables
func (c *LabelCommand) Expand(expander SingleWordExpander) error {
	if c.noExpand {
		return nil
	}
	return expandKvpsInPlace(c.Labels, expander)
}

// SourcesAndDest represent a list of source files and a destination
type SourcesAndDest []string

// Sources list the source paths
func (s SourcesAndDest) Sources() []string {
	res := make([]string, len(s)-1)
	copy(res, s[:len(s)-1])
	return res
}

// Dest path of the operation
func (s SourcesAndDest) Dest() string {
	return s[len(s)-1]
}

// AddCommand : ADD foo /path
//
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
// exist here. If you do not wish to have this automatic handling, use COPY.
//
type AddCommand struct {
	withNameAndCode
	SourcesAndDest
	Chown string
}

// Expand variables
func (c *AddCommand) Expand(expander SingleWordExpander) error {
	return expandSliceInPlace(c.SourcesAndDest, expander)
}

// CopyCommand : COPY foo /path
//
// Same as 'ADD' but without the tar and remote url handling.
//
type CopyCommand struct {
	withNameAndCode
	SourcesAndDest
	From  string
	Chown string
}

// Expand variables
func (c *CopyCommand) Expand(expander SingleWordExpander) error {
	return expandSliceInPlace(c.SourcesAndDest, expander)
}

// OnbuildCommand : ONBUILD <some other command>
type OnbuildCommand struct {
	withNameAndCode
	Expression string
}

// WorkdirCommand : WORKDIR /tmp
//
// Set the working directory for future RUN/CMD/etc statements.
//
type WorkdirCommand struct {
	withNameAndCode
	Path string
}

// Expand variables
func (c *WorkdirCommand) Expand(expander SingleWordExpander) error {
	p, err := expander(c.Path)
	if err != nil {
		return err
	}
	c.Path = p
	return nil
}

// ShellDependantCmdLine represents a cmdline optionally prepended with the shell
type ShellDependantCmdLine struct {
	CmdLine      strslice.StrSlice
	PrependShell bool
}

// RunCommand : RUN some command yo
//
// run a command and commit the image. Args are automatically prepended with
// the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under
// Windows, in the event there is only one argument The difference in processing:
//
// RUN echo hi          # sh -c echo hi       (Linux)
// RUN echo hi          # cmd /S /C echo hi   (Windows)
// RUN [ "echo", "hi" ] # echo hi
//
type RunCommand struct {
	withNameAndCode
	ShellDependantCmdLine
}

// CmdCommand : CMD foo
//
// Set the default command to run in the container (which may be empty).
// Argument handling is the same as RUN.
//
type CmdCommand struct {
	withNameAndCode
	ShellDependantCmdLine
}

// HealthCheckCommand : HEALTHCHECK foo
//
// Set the default healthcheck command to run in the container (which may be empty).
// Argument handling is the same as RUN.
//
type HealthCheckCommand struct {
	withNameAndCode
	Health *container.HealthConfig
}

// EntrypointCommand : ENTRYPOINT /usr/sbin/nginx
//
// Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments
// to /usr/sbin/nginx. Uses the default shell if not in JSON format.
//
// Handles command processing similar to CMD and RUN, only req.runConfig.Entrypoint
// is initialized at newBuilder time instead of through argument parsing.
//
type EntrypointCommand struct {
	withNameAndCode
	ShellDependantCmdLine
}

// ExposeCommand : EXPOSE 6667/tcp 7000/tcp
//
// Expose ports for links and port mappings. This all ends up in
// req.runConfig.ExposedPorts for runconfig.
//
type ExposeCommand struct {
	withNameAndCode
	Ports []string
}

// UserCommand : USER foo
//
// Set the user to 'foo' for future commands and when running the
// ENTRYPOINT/CMD at container run time.
//
type UserCommand struct {
	withNameAndCode
	User string
}

// Expand variables
func (c *UserCommand) Expand(expander SingleWordExpander) error {
	p, err := expander(c.User)
	if err != nil {
		return err
	}
	c.User = p
	return nil
}

// VolumeCommand : VOLUME /foo
//
// Expose the volume /foo for use. Will also accept the JSON array form.
//
type VolumeCommand struct {
	withNameAndCode
	Volumes []string
}

// Expand variables
func (c *VolumeCommand) Expand(expander SingleWordExpander) error {
	return expandSliceInPlace(c.Volumes, expander)
}

// StopSignalCommand : STOPSIGNAL signal
//
// Set the signal that will be used to kill the container.
type StopSignalCommand struct {
	withNameAndCode
	Signal string
}

// Expand variables
func (c *StopSignalCommand) Expand(expander SingleWordExpander) error {
	p, err := expander(c.Signal)
	if err != nil {
		return err
	}
	c.Signal = p
	return nil
}

// CheckPlatform checks that the command is supported in the target platform
func (c *StopSignalCommand) CheckPlatform(platform string) error {
	if platform == "windows" {
		return errors.New("The daemon on this platform does not support the command stopsignal")
	}
	return nil
}

// ArgCommand : ARG name[=value]
//
// Adds the variable foo to the trusted list of variables that can be passed
// to builder using the --build-arg flag for expansion/substitution or passing to 'run'.
// Dockerfile author may optionally set a default value of this variable.
type ArgCommand struct {
	withNameAndCode
	Key   string
	Value *string
}

// Expand variables
func (c *ArgCommand) Expand(expander SingleWordExpander) error {
	p, err := expander(c.Key)
	if err != nil {
		return err
	}
	c.Key = p
	if c.Value != nil {
		p, err = expander(*c.Value)
		if err != nil {
			return err
		}
		c.Value = &p
	}
	return nil
}

// ShellCommand : SHELL powershell -command
//
// Set the non-default shell to use.
type ShellCommand struct {
	withNameAndCode
	Shell strslice.StrSlice
}

// Stage represents a single stage in a multi-stage build
type Stage struct {
	Name       string
	Commands   []Command
	BaseName   string
	SourceCode string
	Platform   specs.Platform
}

// AddCommand to the stage
func (s *Stage) AddCommand(cmd Command) {
	// todo: validate cmd type
	s.Commands = append(s.Commands, cmd)
}

// IsCurrentStage check if the stage name is the current stage
func IsCurrentStage(s []Stage, name string) bool {
	if len(s) == 0 {
		return false
	}
	return s[len(s)-1].Name == name
}

// CurrentStage return the last stage in a slice
func CurrentStage(s []Stage) (*Stage, error) {
	if len(s) == 0 {
		return nil, errors.New("No build stage in current context")
	}
	return &s[len(s)-1], nil
}

// HasStage looks for the presence of a given stage name
func HasStage(s []Stage, name string) (int, bool) {
	for i, stage := range s {
		// Stage name is case-insensitive by design
		if strings.EqualFold(stage.Name, name) {
			return i, true
		}
	}
	return -1, false
}


text:
"""
Getting started with categorical data
=====================================

Experimental support for categorical data.  After 1.5 XGBoost `gpu_hist` tree method has
experimental support for one-hot encoding based tree split, and in 1.6 `approx` support
was added.

In before, users need to run an encoder themselves before passing the data into XGBoost,
which creates a sparse matrix and potentially increase memory usage.  This demo
showcases the experimental categorical data support, more advanced features are planned.

Also, see :doc:`the tutorial </tutorials/categorical>` for using XGBoost with
categorical data.

    .. versionadded:: 1.5.0

"""
from typing import Tuple

import numpy as np
import pandas as pd

import xgboost as xgb

def make_categorical(
    n_samples: int, n_features: int, n_categories: int, onehot: bool
) -> Tuple[pd.DataFrame, pd.Series]:
    """Make some random data for demo."""
    rng = np.random.RandomState(1994)

    pd_dict = {}
    for i in range(n_features + 1):
        c = rng.randint(low=0, high=n_categories, size=n_samples)
        pd_dict[str(i)] = pd.Series(c, dtype=np.int64)

    df = pd.DataFrame(pd_dict)
    label = df.iloc[:, 0]
    df = df.iloc[:, 1:]
    for i in range(0, n_features):
        label += df.iloc[:, i]
    label += 1

    df = df.astype("category")
    categories = np.arange(0, n_categories)
    for col in df.columns:
        df[col] = df[col].cat.set_categories(categories)

    if onehot:
        return pd.get_dummies(df), label
    return df, label

def main() -> None:
    # Use builtin categorical data support
    # For scikit-learn interface, the input data must be pandas DataFrame or cudf
    # DataFrame with categorical features
    X, y = make_categorical(100, 10, 4, False)
    # Specify `enable_categorical` to True, also we use onehot encoding based split
    # here for demonstration. For details see the document of `max_cat_to_onehot`.
    reg = xgb.XGBRegressor(
        tree_method="gpu_hist", enable_categorical=True, max_cat_to_onehot=5
    )
    reg.fit(X, y, eval_set=[(X, y)])

    # Pass in already encoded data
    X_enc, y_enc = make_categorical(100, 10, 4, True)
    reg_enc = xgb.XGBRegressor(tree_method="gpu_hist")
    reg_enc.fit(X_enc, y_enc, eval_set=[(X_enc, y_enc)])

    reg_results = np.array(reg.evals_result()["validation_0"]["rmse"])
    reg_enc_results = np.array(reg_enc.evals_result()["validation_0"]["rmse"])

    # Check that they have same results
    np.testing.assert_allclose(reg_results, reg_enc_results)

    # Convert to DMatrix for SHAP value
    booster: xgb.Booster = reg.get_booster()
    m = xgb.DMatrix(X, enable_categorical=True)  # specify categorical data support.
    SHAP = booster.predict(m, pred_contribs=True)
    margin = booster.predict(m, output_margin=True)
    np.testing.assert_allclose(
        np.sum(SHAP, axis=len(SHAP.shape) - 1), margin, rtol=1e-3
    )

if __name__ == "__main__":
    main()


text:
<md-toolbar class="md-primary">

Filter Product List

</md-toolbar>

<md-select data-ng-model="vm.selectedSupplier" placeholder="filter on supplier">
  <md-select-label>{{ vm.selectedSupplier ? 'filtering by ' + vm.selectedSupplier.CompanyName : 'filter by supplier' }}</md-select-label>
  <md-option data-ng-value="item" data-ng-repeat="item in vm.suppliers">{{item.CompanyName}}</md-option>
</md-select>

<md-select data-ng-model="vm.selectedCategory" placeholder="filter on category">
  <md-select-label>{{ vm.selectedCategory ? 'filtering by ' + vm.selectedCategory.CategoryName : 'filter by category' }}</md-select-label>
  <md-option data-ng-value="item" data-ng-repeat="item in vm.categories">{{item.CategoryName}}</md-option>
</md-select>

<button data-ng-click="vm.clearFilters()">clear filters</button>
<button data-ng-click="vm.addProducts()">add products</button>

<md-toolbar class="md-primary">

Products

</md-toolbar>

<md-list>
  <md-item data-ng-repeat="product in vm.products">
    <md-item-content>

        <md-checkbox data-ng-model="product.isSelected"
                     class="md-primary" aria-label="product.ProductName"></md-checkbox>

{{product.ProductName}}

    </md-item-content>
  </md-item>
</md-list>


text:
namespace SimpleTwitchBot.Lib.Models
{
    public enum TwitchUserType
    {
        Viewer,
        Moderator,
        GlobalModerator,
        Admin,
        Staff
    }
}


text:
<?php

namespace AbstractModels;

abstract class AbstractRole extends \ModelBase
{

    /**
     *
     * @var integer
     */
    public $role_id;

    /**
     *
     * @var string
     */
    public $role_name;

    /**
     *
     * @var string
     */
    public $action_list;

    /**
     *
     * @var string
     */
    public $role_describe;

    /**
     * Returns table name mapped in the model.
     *
     * @return string
     */
    public function getSource()
    {
        return 'role';
    }

    /**
     * Allows to query a set of records that match the specified conditions
     *
     * @param mixed $parameters
     * @return AbstractRole[]
     */
    public static function find($parameters = null)
    {
        return parent::find($parameters);
    }

    /**
     * Allows to query the first record that match the specified conditions
     *
     * @param mixed $parameters
     * @return AbstractRole
     */
    public static function findFirst($parameters = null)
    {
        return parent::findFirst($parameters);
    }

    /**
     * Independent Column Mapping.
     * Keys are the real names in the table and the values their names in the application
     *
     * @return array
     */
    public function columnMap()
    {
        return array(
            'role_id' => 'role_id',
            'role_name' => 'role_name',
            'action_list' => 'action_list',
            'role_describe' => 'role_describe'
        );
    }

}


text:
// The MIT License
//
// Copyright (c) 2014 Gwendal Roué
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#import <Foundation/Foundation.h>
#import "GRMustacheAvailabilityMacros_private.h"
#import "GRMustacheTemplateASTNode_private.h"

@class GRMustachePartialNode;
@class GRMustacheTemplateAST;

/**
 * A GRMustacheInheritedPartialNode is an AST node that represents inherited
 * partials as `{{<name}}...{{/name}}`.
 */
@interface GRMustacheInheritedPartialNode : NSObject<GRMustacheTemplateASTNode> {
@private
    GRMustachePartialNode *_parentPartialNode;
    GRMustacheTemplateAST *_overridingTemplateAST;
}

/**
 * The overriding AST, built from the inner content of the inherited partial
 * node:
 *
 *     {{< ... }} AST {{/ }}
 */
@property (nonatomic, retain, readonly) GRMustacheTemplateAST *overridingTemplateAST GRMUSTACHE_API_INTERNAL;

/**
 * The partial template that is inherited:
 *
 *    {{< inherited_partial_template }}...{{/ }}
 */
@property (nonatomic, retain, readonly) GRMustachePartialNode *parentPartialNode GRMUSTACHE_API_INTERNAL;

/**
 * Builds a GRMustacheInheritedPartialNode.
 *
 * @param parentPartialNode  The inherited partial.
 * @param templateAST        The AST that overrides the inherited partial template.
 *
 * @return A GRMustacheInheritedPartialNode
 */
+ (instancetype)inheritedPartialNodeWithParentPartialNode:(GRMustachePartialNode *)parentPartialNode overridingTemplateAST:(GRMustacheTemplateAST *)overridingTemplateAST GRMUSTACHE_API_INTERNAL;

@end


text:
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.batch;

import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING;
import static google.registry.model.domain.Period.Unit.YEARS;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_CREATE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.assertBillingEventsForResource;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.getHistoryEntriesOfType;
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import google.registry.flows.custom.DomainPricingCustomLogic;
import google.registry.flows.domain.DomainPricingLogic;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.common.Cursor;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.Period;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Registry;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

/** Unit tests for {@link ExpandRecurringBillingEventsAction}. */
public class ExpandRecurringBillingEventsActionTest {

  @RegisterExtension
  public final AppEngineExtension appEngine =
      AppEngineExtension.builder().withCloudSql().withLocalModules().withTaskQueue().build();

  private DateTime currentTestTime = DateTime.parse("1999-01-05T00:00:00Z");
  private final FakeClock clock = new FakeClock(currentTestTime);

  private ExpandRecurringBillingEventsAction action;
  private Domain domain;
  private DomainHistory historyEntry;
  private BillingEvent.Recurring recurring;

  @BeforeEach
  void beforeEach() {
    action = new ExpandRecurringBillingEventsAction();
    action.clock = clock;
    action.cursorTimeParam = Optional.empty();
    action.batchSize = 2;
    action.domainPricingLogic =
        new DomainPricingLogic(new DomainPricingCustomLogic(null, null, null));
    createTld("tld");
    domain =
        persistResource(
            DatabaseHelper.newDomain("example.tld")
                .asBuilder()
                .setCreationTimeForTest(DateTime.parse("1999-01-05T00:00:00Z"))
                .build());
    historyEntry =
        persistResource(
            new DomainHistory.Builder()
                .setRegistrarId(domain.getCreationRegistrarId())
                .setType(HistoryEntry.Type.DOMAIN_CREATE)
                .setModificationTime(DateTime.parse("1999-01-05T00:00:00Z"))
                .setDomain(domain)
                .build());
    recurring =
        new BillingEvent.Recurring.Builder()
            .setDomainHistory(historyEntry)
            .setRegistrarId(domain.getCreationRegistrarId())
            .setEventTime(DateTime.parse("2000-01-05T00:00:00Z"))
            .setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
            .setId(2L)
            .setReason(Reason.RENEW)
            .setRecurrenceEndTime(END_OF_TIME)
            .setTargetId(domain.getDomainName())
            .build();
    currentTestTime = clock.nowUtc();
    clock.setTo(DateTime.parse("2000-10-02T00:00:00Z"));
  }

  private void saveCursor(final DateTime cursorTime) {
    tm().transact(() -> tm().put(Cursor.createGlobal(RECURRING_BILLING, cursorTime)));
  }

  private void runAction() throws Exception {
    action.response = new FakeResponse();
    action.run();
    // Need to save the current test time before running the action, which increments the clock.
    // The execution time (e.g. transaction time) is captured when the action starts running so
    // the passage of time afterward does not affect the timestamp stored in the billing events.
    currentTestTime = clock.nowUtc();
  }

  private void assertCursorAt(DateTime expectedCursorTime) {
    Cursor cursor = tm().transact(() -> tm().loadByKey(Cursor.createGlobalVKey(RECURRING_BILLING)));
    assertThat(cursor).isNotNull();
    assertThat(cursor.getCursorTime()).isEqualTo(expectedCursorTime);
  }

  private void assertHistoryEntryMatches(
      Domain domain,
      DomainHistory actual,
      String registrarId,
      DateTime billingTime,
      boolean shouldHaveTxRecord) {
    assertThat(actual.getBySuperuser()).isFalse();
    assertThat(actual.getRegistrarId()).isEqualTo(registrarId);
    assertThat(actual.getRepoId()).isEqualTo(domain.getRepoId());
    assertThat(actual.getPeriod()).isEqualTo(Period.create(1, YEARS));
    assertThat(actual.getReason())
        .isEqualTo("Domain autorenewal by ExpandRecurringBillingEventsAction");
    assertThat(actual.getRequestedByRegistrar()).isFalse();
    assertThat(actual.getType()).isEqualTo(DOMAIN_AUTORENEW);
    if (shouldHaveTxRecord) {
      assertThat(actual.getDomainTransactionRecords())
          .containsExactly(
              DomainTransactionRecord.create(
                  "tld", billingTime, TransactionReportField.NET_RENEWS_1_YR, 1));
    } else {
      assertThat(actual.getDomainTransactionRecords()).isEmpty();
    }
  }

  private OneTime.Builder defaultOneTimeBuilder() {
    return new BillingEvent.OneTime.Builder()
        .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z"))
        .setRegistrarId("TheRegistrar")
        .setCost(Money.of(USD, 11))
        .setEventTime(DateTime.parse("2000-01-05T00:00:00Z"))
        .setFlags(ImmutableSet.of(Flag.AUTO_RENEW, Flag.SYNTHETIC))
        .setPeriodYears(1)
        .setReason(Reason.RENEW)
        .setSyntheticCreationTime(currentTestTime)
        .setCancellationMatchingBillingEvent(recurring)
        .setTargetId(domain.getDomainName());
  }

  @Test
  void testSuccess_expandSingleEvent() throws Exception {
    persistResource(recurring);
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_deletedDomain() throws Exception {
    DateTime deletionTime = DateTime.parse("2000-08-01T00:00:00Z");
    Domain deletedDomain = persistDeletedDomain("deleted.tld", deletionTime);
    historyEntry =
        persistResource(
            new DomainHistory.Builder()
                .setDomain(deletedDomain)
                .setRegistrarId(deletedDomain.getCreationRegistrarId())
                .setModificationTime(deletedDomain.getCreationTime())
                .setType(DOMAIN_CREATE)
                .build());
    recurring =
        persistResource(
            new BillingEvent.Recurring.Builder()
                .setDomainHistory(historyEntry)
                .setRegistrarId(deletedDomain.getCreationRegistrarId())
                .setEventTime(DateTime.parse("2000-01-05T00:00:00Z"))
                .setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
                .setId(2L)
                .setReason(Reason.RENEW)
                .setRecurrenceEndTime(deletionTime)
                .setTargetId(deletedDomain.getDomainName())
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(deletedDomain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        deletedDomain,
        persistedEntry,
        "TheRegistrar",
        DateTime.parse("2000-02-19T00:00:00Z"),
        true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setDomainHistory(persistedEntry)
            .setTargetId(deletedDomain.getDomainName())
            .build();
    assertBillingEventsForResource(deletedDomain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_idempotentForDuplicateRuns() throws Exception {
    persistResource(recurring);
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).build();
    assertCursorAt(currentTestTime);
    DateTime beginningOfSecondRun = clock.nowUtc();
    action.response = new FakeResponse();
    runAction();
    assertCursorAt(beginningOfSecondRun);
    assertBillingEventsForResource(domain, expected, recurring);
  }

  @Test
  void testSuccess_expandSingleEvent_idempotentForExistingOneTime() throws Exception {
    persistResource(recurring);
    BillingEvent.OneTime persisted =
        persistResource(defaultOneTimeBuilder().setDomainHistory(historyEntry).build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    // No new history entries should be generated
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    assertCursorAt(currentTestTime);
    // No additional billing events should be generated
    assertBillingEventsForResource(domain, persisted, recurring);
  }

  @Test
  void testSuccess_expandSingleEvent_notIdempotentForDifferentBillingTime() throws Exception {
    persistResource(recurring);
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).build();
    // Persist an otherwise identical billing event that differs only in billing time (and ID).
    BillingEvent.OneTime persisted =
        persistResource(
            expected
                .asBuilder()
                .setId(15891L)
                .setBillingTime(DateTime.parse("1999-02-19T00:00:00Z"))
                .setEventTime(DateTime.parse("1999-01-05T00:00:00Z"))
                .build());
    assertCursorAt(currentTestTime);
    assertBillingEventsForResource(domain, persisted, expected, recurring);
  }

  @Test
  void testSuccess_expandSingleEvent_notIdempotentForDifferentRecurring() throws Exception {
    persistResource(recurring);
    BillingEvent.Recurring recurring2 = persistResource(recurring.asBuilder().setId(3L).build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    List<DomainHistory> persistedEntries =
        getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    for (DomainHistory persistedEntry : persistedEntries) {
      assertHistoryEntryMatches(
          domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    }
    assertThat(persistedEntries).hasSize(2);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntries.get(0)).build();
    // Persist an otherwise identical billing event that differs only in recurring event key.
    BillingEvent.OneTime persisted =
        expected
            .asBuilder()
            .setDomainHistory(persistedEntries.get(1))
            .setCancellationMatchingBillingEvent(recurring2)
            .build();
    assertCursorAt(currentTestTime);
    assertBillingEventsForResource(domain, persisted, expected, recurring, recurring2);
  }

  @Test
  void testSuccess_ignoreRecurringBeforeWindow() throws Exception {
    recurring =
        persistResource(
            recurring
                .asBuilder()
                .setEventTime(DateTime.parse("1997-01-05T00:00:00Z"))
                .setRecurrenceEndTime(DateTime.parse("1999-10-05T00:00:00Z"))
                .build());
    action.cursorTimeParam = Optional.of(DateTime.parse("2000-01-01T00:00:00Z"));
    runAction();
    // No new history entries should be generated
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    assertBillingEventsForResource(domain, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_ignoreRecurringAfterWindow() throws Exception {
    recurring =
        persistResource(recurring.asBuilder().setEventTime(clock.nowUtc().plusYears(2)).build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    // No new history entries should be generated
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    assertBillingEventsForResource(domain, recurring);
  }

  @Test
  void testSuccess_expandSingleEvent_billingTimeAtCursorTime() throws Exception {
    persistResource(recurring);
    action.cursorTimeParam = Optional.of(DateTime.parse("2000-02-19T00:00:00Z"));
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_cursorTimeBetweenEventAndBillingTime() throws Exception {
    persistResource(recurring);
    action.cursorTimeParam = Optional.of(DateTime.parse("2000-01-12T00:00:00Z"));
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_billingTimeAtExecutionTime() throws Exception {
    clock.setTo(currentTestTime);
    persistResource(recurring);
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    clock.setTo(DateTime.parse("2000-02-19T00:00:00Z"));
    runAction();
    // No new history entries should be generated
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    // A candidate billing event is set to be billed exactly on 2/19/00 @ 00:00,
    // but these should not be generated as the interval is closed on cursorTime, open on
    // executeTime.
    assertBillingEventsForResource(domain, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_multipleYearCreate() throws Exception {
    action.cursorTimeParam = Optional.of(recurring.getEventTime());
    recurring =
        persistResource(
            recurring.asBuilder().setEventTime(recurring.getEventTime().plusYears(2)).build());
    clock.setTo(DateTime.parse("2002-10-02T00:00:00Z"));
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2002-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setBillingTime(DateTime.parse("2002-02-19T00:00:00Z"))
            .setEventTime(DateTime.parse("2002-01-05T00:00:00Z"))
            .setDomainHistory(persistedEntry)
            .build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_withCursor() throws Exception {
    persistResource(recurring);
    saveCursor(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_withCursorPastExpected() throws Exception {
    persistResource(recurring);
    // Simulate a quick second run of the action (this should be a no-op).
    saveCursor(clock.nowUtc().minusSeconds(1));
    runAction();
    // No new history entries should be generated
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    assertBillingEventsForResource(domain, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_recurrenceEndBeforeEvent() throws Exception {
    // This can occur when a domain is transferred or deleted before a domain comes up for renewal.
    recurring =
        persistResource(
            recurring
                .asBuilder()
                .setRecurrenceEndTime(recurring.getEventTime().minusDays(5))
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    // No new history entries should be generated
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    assertBillingEventsForResource(domain, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_noEventExpanded_recurrenceEndAfterEvent_cursorTimeTooLate() throws Exception {
    // This can occur when a domain is transferred/renewed/deleted during autorenew grace peroid.
    recurring =
        persistResource(
            recurring
                .asBuilder()
                .setRecurrenceEndTime(recurring.getEventTime().plusDays(5))
                .build());
    action.cursorTimeParam = Optional.of(recurring.getRecurrenceEndTime().plusDays(46));
    runAction();
    // No new history entries should be generated because cursor time is more than 45 days after
    // recurrence end time.
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    assertBillingEventsForResource(domain, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_recurrenceEndAfterEvent() throws Exception {
    // This can occur when a domain is transferred/renewed/deleted during autorenew grace peroid.
    recurring =
        persistResource(
            recurring
                .asBuilder()
                .setRecurrenceEndTime(recurring.getEventTime().plusDays(5))
                .build());
    action.cursorTimeParam = Optional.of(recurring.getRecurrenceEndTime().plusDays(35));
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), false);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_dryRun() throws Exception {
    persistResource(recurring);
    action.isDryRun = true;
    saveCursor(START_OF_TIME); // Need a saved cursor to verify that it didn't move.
    runAction();
    // No new history entries should be generated
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    assertBillingEventsForResource(domain, recurring);
    assertCursorAt(START_OF_TIME); // Cursor doesn't move on a dry run.
  }

  @Test
  void testSuccess_expandSingleEvent_multipleYears() throws Exception {
    clock.setTo(clock.nowUtc().plusYears(5));
    List<BillingEvent> expectedEvents = new ArrayList<>();
    expectedEvents.add(persistResource(recurring));
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    List<DomainHistory> persistedEntries =
        getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertThat(persistedEntries).hasSize(6);
    DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z");
    DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z");
    // Expecting events for '00, '01, '02, '03, '04, '05.
    for (int year = 0; year < 6; year++) {
      assertHistoryEntryMatches(
          domain, persistedEntries.get(year), "TheRegistrar", billingDate.plusYears(year), true);
      expectedEvents.add(
          defaultOneTimeBuilder()
              .setBillingTime(billingDate.plusYears(year))
              .setEventTime(eventDate.plusYears(year))
              .setDomainHistory(persistedEntries.get(year))
              .build());
    }
    assertBillingEventsForResource(domain, Iterables.toArray(expectedEvents, BillingEvent.class));
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_multipleYears_cursorInBetweenYears() throws Exception {
    clock.setTo(clock.nowUtc().plusYears(5));
    List<BillingEvent> expectedEvents = new ArrayList<>();
    expectedEvents.add(persistResource(recurring));
    saveCursor(DateTime.parse("2003-10-02T00:00:00Z"));
    runAction();
    List<DomainHistory> persistedEntries =
        getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertThat(persistedEntries).hasSize(2);
    DateTime eventDate = DateTime.parse("2004-01-05T00:00:00Z");
    DateTime billingDate = DateTime.parse("2004-02-19T00:00:00Z");
    // Only expect the last two years' worth of billing events.
    for (int year = 0; year < 2; year++) {
      assertHistoryEntryMatches(
          domain, persistedEntries.get(year), "TheRegistrar", billingDate.plusYears(year), true);
      expectedEvents.add(
          defaultOneTimeBuilder()
              .setBillingTime(billingDate.plusYears(year))
              .setDomainHistory(persistedEntries.get(year))
              .setEventTime(eventDate.plusYears(year))
              .build());
    }
    assertBillingEventsForResource(domain, Iterables.toArray(expectedEvents, BillingEvent.class));
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_singleEvent_beforeRenewal() throws Exception {
    // Need to restore to the time before the clock was advanced so that the commit log's timestamp
    // is not inverted when the clock is later reverted.
    clock.setTo(currentTestTime);
    persistResource(recurring);
    clock.setTo(DateTime.parse("2000-01-04T00:00:00Z"));
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    // No new history entries should be generated
    assertThat(getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW)).isEmpty();
    assertBillingEventsForResource(domain, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_singleEvent_afterRecurrenceEnd_inAutorenewGracePeriod() throws Exception {
    // The domain creation date is 1999-01-05, and the first renewal date is thus 2000-01-05.
    clock.setTo(DateTime.parse("2001-02-06T00:00:00Z"));
    recurring =
        persistResource(
            recurring
                .asBuilder()
                // The domain deletion date is 2000-01-29, which is within the 45 day autorenew
                // grace period
                // from the renewal date.
                .setRecurrenceEndTime(DateTime.parse("2000-01-29T00:00:00Z"))
                .setEventTime(domain.getCreationTime().plusYears(1))
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), false);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z"))
            .setDomainHistory(persistedEntry)
            .build();
    assertBillingEventsForResource(domain, recurring, expected);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_singleEvent_afterRecurrenceEnd_outsideAutorenewGracePeriod() throws Exception {
    // The domain creation date is 1999-01-05, and the first renewal date is thus 2000-01-05.
    clock.setTo(DateTime.parse("2001-02-06T00:00:00Z"));
    recurring =
        persistResource(
            recurring
                .asBuilder()
                // The domain deletion date is 2000-04-05, which is not within the 45 day autorenew
                // grace
                // period from the renewal date.
                .setRecurrenceEndTime(DateTime.parse("2000-04-05T00:00:00Z"))
                .setEventTime(domain.getCreationTime().plusYears(1))
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setBillingTime(DateTime.parse("2000-02-19T00:00:00Z"))
            .setDomainHistory(persistedEntry)
            .build();
    assertBillingEventsForResource(domain, recurring, expected);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_billingTimeOnLeapYear() throws Exception {
    recurring =
        persistResource(
            recurring.asBuilder().setEventTime(DateTime.parse("2000-01-15T00:00:00Z")).build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-29T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setBillingTime(DateTime.parse("2000-02-29T00:00:00Z"))
            .setEventTime(DateTime.parse("2000-01-15T00:00:00Z"))
            .setDomainHistory(persistedEntry)
            .build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandSingleEvent_billingTimeNotOnLeapYear() throws Exception {
    recurring =
        persistResource(
            recurring.asBuilder().setEventTime(DateTime.parse("2001-01-15T00:00:00Z")).build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    clock.setTo(DateTime.parse("2001-12-01T00:00:00Z"));
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2001-03-01T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setBillingTime(DateTime.parse("2001-03-01T00:00:00Z"))
            .setEventTime(DateTime.parse("2001-01-15T00:00:00Z"))
            .setDomainHistory(persistedEntry)
            .build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandMultipleEvents() throws Exception {
    persistResource(recurring);
    Domain domain2 =
        persistResource(
            DatabaseHelper.newDomain("example2.tld")
                .asBuilder()
                .setCreationTimeForTest(DateTime.parse("1999-04-05T00:00:00Z"))
                .build());
    DomainHistory historyEntry2 =
        persistResource(
            new DomainHistory.Builder()
                .setRegistrarId(domain2.getCreationRegistrarId())
                .setType(HistoryEntry.Type.DOMAIN_CREATE)
                .setModificationTime(DateTime.parse("1999-04-05T00:00:00Z"))
                .setDomain(domain2)
                .build());
    BillingEvent.Recurring recurring2 =
        persistResource(
            new BillingEvent.Recurring.Builder()
                .setDomainHistory(historyEntry2)
                .setRegistrarId(domain2.getCreationRegistrarId())
                .setEventTime(DateTime.parse("2000-04-05T00:00:00Z"))
                .setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
                .setReason(Reason.RENEW)
                .setRecurrenceEndTime(END_OF_TIME)
                .setTargetId(domain2.getDomainName())
                .build());
    Domain domain3 =
        persistResource(
            DatabaseHelper.newDomain("example3.tld")
                .asBuilder()
                .setCreationTimeForTest(DateTime.parse("1999-06-05T00:00:00Z"))
                .build());
    DomainHistory historyEntry3 =
        persistResource(
            new DomainHistory.Builder()
                .setRegistrarId(domain3.getCreationRegistrarId())
                .setType(HistoryEntry.Type.DOMAIN_CREATE)
                .setModificationTime(DateTime.parse("1999-06-05T00:00:00Z"))
                .setDomain(domain3)
                .build());
    BillingEvent.Recurring recurring3 =
        persistResource(
            new BillingEvent.Recurring.Builder()
                .setDomainHistory(historyEntry3)
                .setRegistrarId(domain3.getCreationRegistrarId())
                .setEventTime(DateTime.parse("2000-06-05T00:00:00Z"))
                .setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
                .setReason(Reason.RENEW)
                .setRecurrenceEndTime(END_OF_TIME)
                .setTargetId(domain3.getDomainName())
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();

    DomainHistory persistedHistory1 =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedHistory1, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setDomainHistory(persistedHistory1)
            .setCancellationMatchingBillingEvent(recurring)
            .build();
    DomainHistory persistedHistory2 =
        getOnlyHistoryEntryOfType(domain2, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain2, persistedHistory2, "TheRegistrar", DateTime.parse("2000-05-20T00:00:00Z"), true);
    BillingEvent.OneTime expected2 =
        defaultOneTimeBuilder()
            .setBillingTime(DateTime.parse("2000-05-20T00:00:00Z"))
            .setEventTime(DateTime.parse("2000-04-05T00:00:00Z"))
            .setDomainHistory(persistedHistory2)
            .setTargetId(domain2.getDomainName())
            .setCancellationMatchingBillingEvent(recurring2)
            .build();
    DomainHistory persistedHistory3 =
        getOnlyHistoryEntryOfType(domain3, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain3, persistedHistory3, "TheRegistrar", DateTime.parse("2000-07-20T00:00:00Z"), true);
    BillingEvent.OneTime expected3 =
        defaultOneTimeBuilder()
            .setBillingTime(DateTime.parse("2000-07-20T00:00:00Z"))
            .setEventTime(DateTime.parse("2000-06-05T00:00:00Z"))
            .setTargetId(domain3.getDomainName())
            .setDomainHistory(persistedHistory3)
            .setCancellationMatchingBillingEvent(recurring3)
            .build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertBillingEventsForResource(domain2, expected2, recurring2);
    assertBillingEventsForResource(domain3, expected3, recurring3);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandMultipleEvents_anchorTenant() throws Exception {
    persistResource(
        Registry.get("tld")
            .asBuilder()
            .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100"))
            .build());
    recurring = persistResource(recurring.asBuilder().setRenewalPriceBehavior(NONPREMIUM).build());
    BillingEvent.Recurring recurring2 =
        persistResource(
            recurring
                .asBuilder()
                .setEventTime(recurring.getEventTime().plusMonths(3))
                .setId(3L)
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    List<DomainHistory> persistedEntries =
        getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertThat(persistedEntries).hasSize(2);
    assertHistoryEntryMatches(
        domain,
        persistedEntries.get(0),
        "TheRegistrar",
        DateTime.parse("2000-02-19T00:00:00Z"),
        true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setDomainHistory(persistedEntries.get(0))
            .setCancellationMatchingBillingEvent(recurring)
            .build();
    assertHistoryEntryMatches(
        domain,
        persistedEntries.get(1),
        "TheRegistrar",
        DateTime.parse("2000-05-20T00:00:00Z"),
        true);
    BillingEvent.OneTime expected2 =
        defaultOneTimeBuilder()
            .setBillingTime(DateTime.parse("2000-05-20T00:00:00Z"))
            .setEventTime(DateTime.parse("2000-04-05T00:00:00Z"))
            .setDomainHistory(persistedEntries.get(1))
            .setCancellationMatchingBillingEvent(recurring2)
            .build();
    assertBillingEventsForResource(domain, expected, expected2, recurring, recurring2);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_expandMultipleEvents_premiumDomain_internalRegistration() throws Exception {
    persistResource(
        Registry.get("tld")
            .asBuilder()
            .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100"))
            .build());
    recurring =
        persistResource(
            recurring
                .asBuilder()
                .setRenewalPriceBehavior(SPECIFIED)
                .setRenewalPrice(Money.of(USD, 4))
                .build());
    BillingEvent.Recurring recurring2 =
        persistResource(
            recurring
                .asBuilder()
                .setEventTime(recurring.getEventTime().plusMonths(3))
                .setId(3L)
                .setRenewalPriceBehavior(SPECIFIED)
                .setRenewalPrice(Money.of(USD, 4))
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    List<DomainHistory> persistedEntries =
        getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertThat(persistedEntries).hasSize(2);
    assertHistoryEntryMatches(
        domain,
        persistedEntries.get(0),
        "TheRegistrar",
        DateTime.parse("2000-02-19T00:00:00Z"),
        true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setCost(Money.of(USD, 4))
            .setDomainHistory(persistedEntries.get(0))
            .setCancellationMatchingBillingEvent(recurring)
            .build();
    assertHistoryEntryMatches(
        domain,
        persistedEntries.get(1),
        "TheRegistrar",
        DateTime.parse("2000-05-20T00:00:00Z"),
        true);
    BillingEvent.OneTime expected2 =
        defaultOneTimeBuilder()
            .setCost(Money.of(USD, 4))
            .setBillingTime(DateTime.parse("2000-05-20T00:00:00Z"))
            .setEventTime(DateTime.parse("2000-04-05T00:00:00Z"))
            .setDomainHistory(persistedEntries.get(1))
            .setCancellationMatchingBillingEvent(recurring2)
            .build();
    assertBillingEventsForResource(domain, expected, expected2, recurring, recurring2);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_premiumDomain() throws Exception {
    persistResource(
        Registry.get("tld")
            .asBuilder()
            .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100"))
            .build());
    persistResource(recurring);
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder()
            .setDomainHistory(persistedEntry)
            .setCost(Money.of(USD, 100))
            .build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_premiumDomain_forAnchorTenant() throws Exception {
    persistResource(
        Registry.get("tld")
            .asBuilder()
            .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100"))
            .build());
    recurring = persistResource(recurring.asBuilder().setRenewalPriceBehavior(NONPREMIUM).build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).setCost(Money.of(USD, 11)).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_standardDomain_forAnchorTenant() throws Exception {
    recurring = persistResource(recurring.asBuilder().setRenewalPriceBehavior(NONPREMIUM).build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_premiumDomain_forInternalRegistration() throws Exception {
    persistResource(
        Registry.get("tld")
            .asBuilder()
            .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100"))
            .build());
    recurring =
        persistResource(
            recurring
                .asBuilder()
                .setRenewalPriceBehavior(SPECIFIED)
                .setRenewalPrice(Money.of(USD, 20))
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).setCost(Money.of(USD, 20)).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_standardDomain_forInternalRegistration() throws Exception {
    recurring =
        persistResource(
            recurring
                .asBuilder()
                .setRenewalPriceBehavior(SPECIFIED)
                .setRenewalPrice(Money.of(USD, 2))
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    DomainHistory persistedEntry =
        getOnlyHistoryEntryOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertHistoryEntryMatches(
        domain, persistedEntry, "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"), true);
    BillingEvent.OneTime expected =
        defaultOneTimeBuilder().setDomainHistory(persistedEntry).setCost(Money.of(USD, 2)).build();
    assertBillingEventsForResource(domain, expected, recurring);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_varyingRenewPrices() throws Exception {
    clock.setTo(currentTestTime);
    persistResource(
        Registry.get("tld")
            .asBuilder()
            .setRenewBillingCostTransitions(
                ImmutableSortedMap.of(
                    START_OF_TIME,
                    Money.of(USD, 8),
                    DateTime.parse("2000-06-01T00:00:00Z"),
                    Money.of(USD, 10)))
            .build());
    clock.setTo(DateTime.parse("2001-10-02T00:00:00Z"));
    persistResource(recurring);
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    List<DomainHistory> persistedEntries =
        getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertThat(persistedEntries).hasSize(2);
    DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z");
    DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z");
    assertHistoryEntryMatches(domain, persistedEntries.get(0), "TheRegistrar", billingDate, true);
    BillingEvent.OneTime cheaper =
        defaultOneTimeBuilder()
            .setBillingTime(billingDate)
            .setEventTime(eventDate)
            .setDomainHistory(persistedEntries.get(0))
            .setCost(Money.of(USD, 8))
            .build();
    assertHistoryEntryMatches(
        domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1), true);
    BillingEvent.OneTime expensive =
        cheaper
            .asBuilder()
            .setCost(Money.of(USD, 10))
            .setBillingTime(billingDate.plusYears(1))
            .setEventTime(eventDate.plusYears(1))
            .setDomainHistory(persistedEntries.get(1))
            .build();
    assertBillingEventsForResource(domain, recurring, cheaper, expensive);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_varyingRenewPrices_anchorTenant() throws Exception {
    clock.setTo(currentTestTime);
    persistResource(
        Registry.get("tld")
            .asBuilder()
            .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100"))
            .setRenewBillingCostTransitions(
                ImmutableSortedMap.of(
                    START_OF_TIME,
                    Money.of(USD, 8),
                    DateTime.parse("2000-06-01T00:00:00Z"),
                    Money.of(USD, 10)))
            .build());
    clock.setTo(DateTime.parse("2001-10-02T00:00:00Z"));
    recurring = persistResource(recurring.asBuilder().setRenewalPriceBehavior(NONPREMIUM).build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    List<DomainHistory> persistedEntries =
        getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertThat(persistedEntries).hasSize(2);
    DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z");
    DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z");
    assertHistoryEntryMatches(domain, persistedEntries.get(0), "TheRegistrar", billingDate, true);
    BillingEvent.OneTime cheaper =
        defaultOneTimeBuilder()
            .setBillingTime(billingDate)
            .setEventTime(eventDate)
            .setDomainHistory(persistedEntries.get(0))
            .setCost(Money.of(USD, 8))
            .build();
    assertHistoryEntryMatches(
        domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1), true);
    BillingEvent.OneTime expensive =
        cheaper
            .asBuilder()
            .setCost(Money.of(USD, 10))
            .setBillingTime(billingDate.plusYears(1))
            .setEventTime(eventDate.plusYears(1))
            .setDomainHistory(persistedEntries.get(1))
            .build();
    assertBillingEventsForResource(domain, recurring, cheaper, expensive);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testSuccess_varyingRenewPrices_internalRegistration() throws Exception {
    clock.setTo(currentTestTime);
    persistResource(
        Registry.get("tld")
            .asBuilder()
            .setPremiumList(persistPremiumList("tld2", USD, "example,USD 100"))
            .setRenewBillingCostTransitions(
                ImmutableSortedMap.of(
                    START_OF_TIME,
                    Money.of(USD, 8),
                    DateTime.parse("2000-06-01T00:00:00Z"),
                    Money.of(USD, 10)))
            .build());
    clock.setTo(DateTime.parse("2001-10-02T00:00:00Z"));
    recurring =
        persistResource(
            recurring
                .asBuilder()
                .setRenewalPriceBehavior(SPECIFIED)
                .setRenewalPrice(Money.of(USD, 5))
                .build());
    action.cursorTimeParam = Optional.of(START_OF_TIME);
    runAction();
    List<DomainHistory> persistedEntries =
        getHistoryEntriesOfType(domain, DOMAIN_AUTORENEW, DomainHistory.class);
    assertThat(persistedEntries).hasSize(2);
    DateTime eventDate = DateTime.parse("2000-01-05T00:00:00Z");
    DateTime billingDate = DateTime.parse("2000-02-19T00:00:00Z");
    assertHistoryEntryMatches(domain, persistedEntries.get(0), "TheRegistrar", billingDate, true);
    BillingEvent.OneTime cheaper =
        defaultOneTimeBuilder()
            .setBillingTime(billingDate)
            .setEventTime(eventDate)
            .setDomainHistory(persistedEntries.get(0))
            .setCost(Money.of(USD, 5))
            .build();
    assertHistoryEntryMatches(
        domain, persistedEntries.get(1), "TheRegistrar", billingDate.plusYears(1), true);
    BillingEvent.OneTime expensive =
        cheaper
            .asBuilder()
            .setCost(Money.of(USD, 5))
            .setBillingTime(billingDate.plusYears(1))
            .setEventTime(eventDate.plusYears(1))
            .setDomainHistory(persistedEntries.get(1))
            .build();
    assertBillingEventsForResource(domain, recurring, cheaper, expensive);
    assertCursorAt(currentTestTime);
  }

  @Test
  void testFailure_cursorAfterExecutionTime() {
    action.cursorTimeParam = Optional.of(clock.nowUtc().plusYears(1));
    IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, this::runAction);
    assertThat(thrown)
        .hasMessageThat()
        .contains("Cursor time must be earlier than execution time.");
  }

  @Test
  void testFailure_cursorAtExecutionTime() {
    // The clock advances one milli on run.
    action.cursorTimeParam = Optional.of(clock.nowUtc().plusMillis(1));
    IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, this::runAction);
    assertThat(thrown)
        .hasMessageThat()
        .contains("Cursor time must be earlier than execution time.");
  }
}